├── .gitignore ├── LICENSE ├── README.md ├── angular ├── .angular-cli.json ├── .editorconfig ├── README.md ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── manifest.yml ├── package.json ├── protractor.conf.js ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routing.ts │ │ ├── home.component.html │ │ └── home.component.ts │ ├── assets │ │ ├── .gitkeep │ │ └── providers-template.json │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── nginx.conf │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json ├── openwhisk-oauth ├── init.sh ├── oauth-login.js ├── oauth-redirect.js └── providers-template.json ├── openwhisk-protected ├── init.sh ├── my-api-swagger.json └── protected-action.js ├── presentation └── OpenWhisk.pdf └── screenshots ├── Oauth1.png ├── Oauth2.png ├── Oauth3.png ├── Oauth4.png ├── Untitled Diagram.png ├── google-api-manager.png ├── openwhisk-api-1.png ├── openwhisk-api-2.png ├── openwhisk-api-3.png ├── presentation.png └── web-app.png /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /angular/dist 5 | /angular/tmp 6 | /angular/out-tsc 7 | 8 | # dependencies 9 | /angular/node_modules 10 | 11 | # IDEs and editors 12 | /angular/.idea 13 | /angular/.project 14 | /angular/.classpath 15 | /angular/.c9/ 16 | /angular/*.launch 17 | /angular/.settings/ 18 | /angular/*.sublime-workspace 19 | 20 | # IDE - VSCode 21 | /angular/.vscode/* 22 | /angular/!.vscode/settings.json 23 | /angular/!.vscode/tasks.json 24 | /angular/!.vscode/launch.json 25 | /angular/!.vscode/extensions.json 26 | 27 | # misc 28 | /angular/.sass-cache 29 | /angular/connect.lock 30 | /angular/coverage 31 | /angular/libpeerconnection.log 32 | /angular/npm-debug.log 33 | /angular/testem.log 34 | /angular/typings 35 | 36 | # e2e 37 | /angular/e2e/*.js 38 | /angular/e2e/*.map 39 | 40 | # System Files 41 | /angular/.DS_Store 42 | /angularThumbs.db 43 | 44 | /openwhisk-oauth/providers.json 45 | /angular/src/assets/providers.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use IBM cloud functions to create an OAuth app with Angular 2 | 3 | This code pattern shows how to write Angular web applications which use IBM cloud functions actions to implement backend logic. Users need to log in with their Google accounts via OAuth. After this users can access IBM cloud functions actions that have been protected via IBM cloud functions API management. Through this exercise, users will get familiar with: 4 | * How to create IBM cloud functions actions. 5 | * How to setup Google OAuth access. 6 | 7 | ## Flow 8 | ![architecture](https://media.github.ibm.com/user/1650/files/82134860-1c61-11e8-8f58-62763114310e) 9 | 10 | 1. User opens Angular via web browser and presses 'login' button. 11 | 2. Angular app opens Google OAuth web page where users authenticate and grant application access. 12 | 3. Google web page redirects to OpenWhisk sequence 'oauth-login-and-redirect' with a 'code' parameter in the URL. 13 | 4. The sequence is triggered. The first OpenWhisk function 'oauth-login' reads the 'code' and invokes a Google API endpoint to exchange the 'code' against a 'token'. 14 | 5. The same 'oauth-login' function invokes with the token another Google API endpoint to read user profile information, for example the user name. 15 | 6. The sequence invokes the next OpenWhisk function 'redirect' which invokes the Angular app with the token and the user name in the url. 16 | 7. When users click on 'invoke protected action' in the Angular app, a REST API to the API management is invoked. The request contains the token. 17 | 8. API management validates the token. If valid, the OpenWhisk function 'protected-action' is invoked. 18 | 9. The response from 'protected-action' is displayed in the Angular app. 19 | 20 | 21 | ## Included Components 22 | * [Angular](https://angular.io/): Angular is a JavaScript-based open-source front-end web application framework. 23 | * [IBM Cloud Functions](https://console.bluemix.net/openwhisk/): A distributed, event-driven compute service also referred to as Serverless computing or as Function as a Service (FaaS). 24 | * [Google OAuth 2.0](https://developers.google.com/identity/protocols/OAuth2UserAgent): Google APIs use the OAuth 2.0 protocol for authentication and authorization. Google supports common OAuth 2.0 scenarios such as those for web server, installed, and client-side applications. 25 | 26 | ## Featured technologies 27 | * Serverless: An event-action platform that allows you to execute code in response to an event. 28 | 29 | ## Pre-requisite 30 | * [Register](https://console.bluemix.net/registration/) an IBM Cloud account. 31 | * [Download](https://console.bluemix.net/openwhisk/learn/cli) the IBM functions CLI. 32 | * Install Angular. Run: 33 | ``` 34 | npm install -g @angular/cli 35 | npm install 36 | ``` 37 | 38 | ## Steps 39 | **1. Clone the repo** 40 | 41 | In a terminal, run: 42 | `git clone https://github.com/IBM/IBM-function-webapp.git` 43 | 44 | **2. Create protected API** 45 | 46 | * Run `wsk bluemix login` or `wsk bluemix login --sso` if you have sso enabled. This command will make you pick a openwisk namespace, like this: 47 | ``` 48 | Select a namespace: 49 | 1. andy.shi_dev 50 | 2. Developer Advocacy_dev 51 | 3. Developer Advocacy_Watson Developer Advocacy 52 | 4. Developer Advocacy_Cloud Developer Advocacy 53 | namespace>1 54 | ok: User 'Andy.Shi@ibm.com' logged into Bluemix 55 | ``` 56 | Choose a namespace from the list and remember it. 57 | 58 | * Modify `openwhisk-protected/my-api-swagger.json`. Replace all the occurances of the namespace with your picked namespace. Here is what it should like after the change: 59 | ``` 60 | 61 | "x-openwhisk": { 62 | "namespace": "andy.shi_dev", 63 | "url": "https://openwhisk.ng.bluemix.net/api/v1/web/andy.shi_dev/default/protected-action.json" 64 | }, 65 | "x-ibm-configuration": { 66 | "assembly": { 67 | "execute": [{ 68 | "operation-switch": { 69 | "case": [{ 70 | "operations": ["getAction"], 71 | "execute": [{ 72 | "invoke": { 73 | "target-url": "https://openwhisk.ng.bluemix.net/api/v1/web/andy.shi_dev/default/protected-action.json" 74 | } 75 | }] 76 | }] 77 | } 78 | }] 79 | } 80 | } 81 | 82 | ``` 83 | 84 | * Run: 85 | ``` 86 | cd openwhisk-protected 87 | /init.sh 88 | ``` 89 | This command will create a "protected action". You should see the result like: 90 | ``` 91 | ok: updated action protected-action 92 | ok: created API /path/action get for action /andy.shi_dev/default/protected-action 93 | https://service.us.apiconnect.ibmcloud.com/gws/apigateway/api/b8c64953ec67f9443f7a79710b0b1aa59f3980f7590bc03b51262b22002c650c/path/action 94 | 95 | ``` 96 | The last url here is needed for the "pretectedUrl" field in step 6. 97 | 98 | **3. Deploy OpenWhisk OAuth Actions** 99 | 100 | * Run: 101 | ``` 102 | cd .. 103 | cd openwhisk-oauth 104 | /init.sh 105 | cd .. 106 | ``` 107 | 108 | **4. Create Google Application** 109 | 110 | * Open the [Google Developers API Console](https://console.developers.google.com/apis). From the left side bars, choose `Credentials` bar. 111 | 112 | ![img1](screenshots/Oauth1.png) 113 | 114 | * From the `Create credentials` menu bar pick `OAuth client ID` 115 | ![img2](screenshots/Oauth2.png) 116 | 117 | * Next you will see the warning message: "To create an OAuth client ID, you must first set a product name on the consent screen". Click "Configure content screen" button and finish the screen. That is pretty straightforward. 118 | 119 | * Coming back to the "Create client id" tab, pick the first choice `Web Application`. You will see the tab expands. 120 | For "Authorized JavaScript origins", enter the domain of IBM cloud functions. For "Authorized redirect URIs", enter the "oauth-login-and-redirect" url(mind the namespace). 121 | ![img3](screenshots/Oauth3.png) 122 | 123 | * Click "Create" button and you will get the client id and secret in a popup. Save that information. 124 | ![img4](screenshots/Oauth4.png) 125 | 126 | **5. Deploy OpenWhisk OAuth Actions again** 127 | 128 | * Open `openwhisk-oauth/providers-template.json` and save it as `openwhisk-oauth/providers.json`. 129 | * Change redirect URL to reflect your namespace. 130 | * Fill in client id, secret from previous step. You should have something like this: 131 | ``` 132 | { 133 | "authorization_type": "Bearer", 134 | "token_endpoint_form": { 135 | "grant_type": "authorization_code", 136 | "redirect_uri": "https://openwhisk.ng.bluemix.net/api/v1/web/andy.shi_dev/default/oauth-login-and-redirect" 137 | }, 138 | "userinfo_identifier": "email", 139 | "credentials": { 140 | "client_id": "453375156318-edqjkls99e9jiv0f1f7of2tvbe1k7k0m.apps.googleusercontent.com", 141 | "client_secret": "rPc_cR7UZomFtOMZ5uT6DBU1" 142 | } 143 | } 144 | ``` 145 | * Run `openwhisk-oauth/init.sh`. 146 | 147 | **6. Configure and run the Angular App** 148 | 149 | * Open `angular/src/assets/providers-template.json`, and save as `angular/src/assets/providers.json`. 150 | * Change redirectUrl, clientId and protectedUrl. "protectedUrl" is acquired at step 2. 151 | ``` 152 | { 153 | "google": { 154 | "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", 155 | "redirectUrl": "https://openwhisk.ng.bluemix.net/api/v1/web/andy.shi_dev/default/oauth-login-and-redirect", 156 | "clientId": "453375156318-edqjkls99e9jiv0f1f7of2tvbe1k7k0m.apps.googleusercontent.com", 157 | "protectedUrl": "https://service.us.apiconnect.ibmcloud.com/gws/apigateway/api/b8c64953ec67f9443f7a79710b0b1aa59f3980f7590bc03b51262b22002c650c/path/action" 158 | } 159 | } 160 | ``` 161 | * Go to `angular` folder and run `ng serve`. 162 | * In a browser, open `localhost:4200`. 163 | * Click on `login` button first to invoke the oauth action. Then click the `Invoke Protected Action` to invoke the protected action. 164 | ![result](screenshots/web-app.png) 165 | 166 | ## Credits 167 | 168 | This developer code pattern is developed by **Niklas Heidloff**. Thanks to **Nick Mitchell** and **Lionel Villard** for their work on the open source project [openwhisk-oauth](https://github.com/starpit/openwhisk-oauth), especially for the OAuth login functionality. 169 | 170 | ## License 171 | 172 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](http://www.apache.org/licenses/LICENSE-2.0.txt). 173 | 174 | [Apache Software License (ASL) FAQ](http://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) -------------------------------------------------------------------------------- /angular/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "openwhisk-angular" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico", 13 | "nginx.conf" 14 | ], 15 | "index": "index.html", 16 | "main": "main.ts", 17 | "polyfills": "polyfills.ts", 18 | "test": "test.ts", 19 | "tsconfig": "tsconfig.app.json", 20 | "testTsconfig": "tsconfig.spec.json", 21 | "prefix": "app", 22 | "styles": [ 23 | "styles.css" 24 | ], 25 | "scripts": [], 26 | "environmentSource": "environments/environment.ts", 27 | "environments": { 28 | "dev": "environments/environment.ts", 29 | "prod": "environments/environment.prod.ts" 30 | } 31 | } 32 | ], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "lint": [ 39 | { 40 | "project": "src/tsconfig.app.json" 41 | }, 42 | { 43 | "project": "src/tsconfig.spec.json" 44 | }, 45 | { 46 | "project": "e2e/tsconfig.e2e.json" 47 | } 48 | ], 49 | "test": { 50 | "karma": { 51 | "config": "./karma.conf.js" 52 | } 53 | }, 54 | "defaults": { 55 | "styleExt": "css", 56 | "component": {} 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /angular/README.md: -------------------------------------------------------------------------------- 1 | # OpenwhiskAngular 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | Before running the tests make sure you are serving the app via `ng serve`. 25 | 26 | ## Further help 27 | 28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 29 | -------------------------------------------------------------------------------- /angular/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { OpenwhiskAngularPage } from './app.po'; 2 | 3 | describe('openwhisk-angular App', () => { 4 | let page: OpenwhiskAngularPage; 5 | 6 | beforeEach(() => { 7 | page = new OpenwhiskAngularPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /angular/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class OpenwhiskAngularPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /angular/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /angular/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /angular/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - host: openwhisk-angular 4 | name: openwhisk-angular 5 | memory: 256M 6 | path: dist/ 7 | instances: 1 8 | stack: cflinuxfs2 9 | buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git 10 | env: 11 | FORCE_HTTPS: true -------------------------------------------------------------------------------- /angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openwhisk-angular", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^4.0.0", 16 | "@angular/common": "^4.0.0", 17 | "@angular/compiler": "^4.0.0", 18 | "@angular/core": "^4.0.0", 19 | "@angular/forms": "^4.0.0", 20 | "@angular/http": "^4.0.0", 21 | "@angular/platform-browser": "^4.0.0", 22 | "@angular/platform-browser-dynamic": "^4.0.0", 23 | "@angular/router": "^4.0.0", 24 | "core-js": "^2.4.1", 25 | "rxjs": "^5.1.0", 26 | "zone.js": "^0.8.4" 27 | }, 28 | "devDependencies": { 29 | "@angular/cli": "1.2.0", 30 | "@angular/compiler-cli": "^4.0.0", 31 | "@angular/language-service": "^4.0.0", 32 | "@types/jasmine": "~2.5.53", 33 | "@types/jasminewd2": "~2.0.2", 34 | "@types/node": "~6.0.60", 35 | "codelyzer": "~3.0.1", 36 | "jasmine-core": "~2.6.2", 37 | "jasmine-spec-reporter": "~4.1.0", 38 | "karma": "~1.7.0", 39 | "karma-chrome-launcher": "~2.1.1", 40 | "karma-cli": "~1.0.1", 41 | "karma-coverage-istanbul-reporter": "^1.2.1", 42 | "karma-jasmine": "~1.1.0", 43 | "karma-jasmine-html-reporter": "^0.2.2", 44 | "protractor": "~5.1.2", 45 | "ts-node": "~3.0.4", 46 | "tslint": "~5.3.2", 47 | "typescript": "~2.3.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /angular/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /angular/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/angular/src/app/app.component.css -------------------------------------------------------------------------------- /angular/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', async(() => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | })); 19 | }); 20 | -------------------------------------------------------------------------------- /angular/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute, Params, Router } from '@angular/router'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /angular/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpModule, RequestOptions, XHRBackend, Http } from "@angular/http"; 4 | import { AppComponent } from './app.component'; 5 | import { HomeComponent } from './home.component'; 6 | import { routing, appRoutingProviders } from './app.routing'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent, HomeComponent 11 | ], 12 | imports: [ 13 | HttpModule, 14 | BrowserModule, 15 | routing 16 | ], 17 | providers: [appRoutingProviders], 18 | bootstrap: [AppComponent] 19 | }) 20 | export class AppModule { } 21 | -------------------------------------------------------------------------------- /angular/src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { AppComponent } from './app.component'; 4 | import { HomeComponent } from './home.component'; 5 | 6 | const appRoutes: Routes = [ 7 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 8 | { path: 'home', component: HomeComponent } 9 | ]; 10 | 11 | export const appRoutingProviders: any[] = [ 12 | ]; 13 | 14 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /angular/src/app/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | {{ userName }} is now logged in 7 |
8 |
9 |
10 |
11 | Result of the protected action: 12 |
13 |
14 | {{ resultOfProtectedAPI }} 15 |
-------------------------------------------------------------------------------- /angular/src/app/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute, Params, Router } from '@angular/router'; 3 | import { Http, Headers, URLSearchParams, RequestOptions } from '@angular/http'; 4 | import * as providers from '../assets/providers.json'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import 'rxjs/Rx'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './home.component.html' 11 | }) 12 | 13 | export class HomeComponent { 14 | 15 | private initialized: boolean = false; 16 | private redirectUrl: string; 17 | private authorizationUrl: string; 18 | private clientId: string; 19 | private protectedUrl: string; 20 | 21 | private accessToken: string; 22 | private refreshToken: string; 23 | private expiresIn: string; 24 | private userName: string; 25 | 26 | private resultOfProtectedAPI; 27 | 28 | onButtonLoginClicked(): void { 29 | if (this.initialized == true) { 30 | let url = this.authorizationUrl + "?scope=email"; 31 | url = url + "&response_type=" + "code"; 32 | url = url + "&client_id=" + this.clientId; 33 | url = url + "&access_type=" + "offline"; 34 | url = url + "&redirect_uri=" + this.redirectUrl; 35 | window.location.href = url; 36 | } 37 | } 38 | 39 | /* 40 | curl --request GET \ 41 | --url https://service.us.apiconnect.ibmcloud.com/gws/apigateway/api/a172f687d4c7fac2da3546903009090a18a9643313d2d6e47ff43dd8ede5fa3a/niklas/action \ 42 | --header 'accept: application/json' \ 43 | --header 'authorization: Bearer REPLACE_BEARER_TOKEN' 44 | */ 45 | 46 | onButtonInvokeActionClicked(): void { 47 | 48 | let headers = new Headers({ 49 | 'Content-Type': 'application/json', 50 | 'authorization': 'Bearer ' + this.accessToken 51 | }); 52 | let options = new RequestOptions({ headers: headers }); 53 | 54 | this.http.get(this.protectedUrl, options) 55 | .map(res => res.json()) 56 | .subscribe( 57 | result => { 58 | console.log(result) 59 | this.resultOfProtectedAPI = JSON.stringify(result, null, 2); 60 | }, 61 | err => { 62 | console.error(err); 63 | this.resultOfProtectedAPI = JSON.stringify(err._body, null, 2); 64 | }); 65 | } 66 | 67 | constructor( 68 | private http: Http, 69 | private route: ActivatedRoute, 70 | private router: Router) { 71 | } 72 | 73 | ngOnInit(): void { 74 | let googleProvider = providers['google']; 75 | if (googleProvider) { 76 | this.redirectUrl = googleProvider['redirectUrl']; 77 | this.authorizationUrl = googleProvider['authorizationUrl']; 78 | this.clientId = googleProvider['clientId']; 79 | this.protectedUrl = googleProvider['protectedUrl']; 80 | if (this.redirectUrl && this.clientId && this.authorizationUrl && this.protectedUrl) { 81 | this.initialized = true; 82 | } 83 | } 84 | 85 | this.accessToken = this.route.snapshot.queryParams["access_token"]; 86 | if (this.accessToken) console.log(this.accessToken) 87 | this.refreshToken = this.route.snapshot.queryParams["refresh_token"]; 88 | if (this.refreshToken) console.log(this.refreshToken) 89 | this.expiresIn = this.route.snapshot.queryParams["expires_in"]; 90 | if (this.expiresIn) console.log(this.expiresIn) 91 | this.userName = this.route.snapshot.queryParams["user_name"]; 92 | if (this.userName) console.log(this.userName) 93 | } 94 | 95 | } 96 | 97 | -------------------------------------------------------------------------------- /angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/angular/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular/src/assets/providers-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "google": { 3 | "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", 4 | "redirectUrl": "https://openwhisk.ng.bluemix.net/api/v1/web/niklas_heidloff@de.ibm.com_dev/default/oauth-login-and-redirect", 5 | "clientId": "xxx", 6 | "protectedUrl": "https://service.us.apiconnect.ibmcloud.com/gws/apigateway/api/xxx" 7 | } 8 | } -------------------------------------------------------------------------------- /angular/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /angular/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/angular/src/favicon.ico -------------------------------------------------------------------------------- /angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenwhiskAngular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /angular/src/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | daemon off; 3 | 4 | error_log /home/vcap/app/nginx/logs/error.log; 5 | events { worker_connections 1024; } 6 | 7 | http { 8 | log_format cloudfoundry '$http_x_forwarded_for - $http_referer - [$time_local] "$request" 9 | $status $body_bytes_sent'; 10 | access_log /home/vcap/app/nginx/logs/access.log cloudfoundry; 11 | default_type application/octet-stream; 12 | include mime.types; 13 | sendfile on; 14 | 15 | tcp_nopush on; 16 | keepalive_timeout 30; 17 | port_in_redirect off; 18 | server_tokens off; 19 | 20 | server { 21 | listen <%= ENV["PORT"] %> ; 22 | server_name localhost; 23 | 24 | 25 | location / { 26 | root /home/vcap/app/public; 27 | try_files $uri /index.html =404; 28 | index index.html; 29 | gzip_static on; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /angular/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /angular/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /angular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /angular/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /angular/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /angular/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | declare module "*.json" { 7 | const value: any; 8 | export default value; 9 | } -------------------------------------------------------------------------------- /angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /angular/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | "static-before-instance", 35 | "variables-before-functions" 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-empty-interface": true, 52 | "no-eval": true, 53 | "no-inferrable-types": [ 54 | true, 55 | "ignore-params" 56 | ], 57 | "no-misused-new": true, 58 | "no-non-null-assertion": true, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-string-throw": true, 62 | "no-switch-case-fall-through": true, 63 | "no-trailing-whitespace": true, 64 | "no-unnecessary-initializer": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "object-literal-sort-keys": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "prefer-const": true, 77 | "quotemark": [ 78 | true, 79 | "single" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | "always" 84 | ], 85 | "triple-equals": [ 86 | true, 87 | "allow-null-check" 88 | ], 89 | "typedef-whitespace": [ 90 | true, 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | } 98 | ], 99 | "typeof-compare": true, 100 | "unified-signatures": true, 101 | "variable-name": false, 102 | "whitespace": [ 103 | true, 104 | "check-branch", 105 | "check-decl", 106 | "check-operator", 107 | "check-separator", 108 | "check-type" 109 | ], 110 | "directive-selector": [ 111 | true, 112 | "attribute", 113 | "app", 114 | "camelCase" 115 | ], 116 | "component-selector": [ 117 | true, 118 | "element", 119 | "app", 120 | "kebab-case" 121 | ], 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true, 131 | "no-access-missing-member": true, 132 | "templates-use-public": true, 133 | "invoke-injectable": true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /openwhisk-oauth/init.sh: -------------------------------------------------------------------------------- 1 | export WSK="${WSK-wsk}" 2 | 3 | PROVIDERS=`cat providers.json` 4 | 5 | $WSK action update oauth-login oauth-login.js --kind nodejs:6 -p providers "${PROVIDERS}" -p provider "google" 6 | 7 | $WSK action update oauth-redirect oauth-redirect.js --kind nodejs:6 -a web-export true -p providers "${PROVIDERS}" -p provider "google" 8 | 9 | $WSK action update --sequence oauth-login-and-redirect oauth-login,oauth-redirect -a web-export true -------------------------------------------------------------------------------- /openwhisk-oauth/oauth-login.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Copyright IBM Corp. 2017 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // Original author: Nick Mitchell https://github.com/starpit/openwhisk-oauth/blob/master/actions/login/login.js 17 | //------------------------------------------------------------------------------ 18 | 19 | var request = require('request'); 20 | 21 | /** 22 | * Listen for requests for oauth logins from the clients 23 | * 24 | * the client is giving has an oauth code; we want to exchange 25 | * this for an access_token, and, from there, for some identifying 26 | * information from the user's profile. first thing's first... 27 | * 28 | */ 29 | function main(params) { 30 | 31 | return new Promise((resolve, reject) => { 32 | // here are the provider secrets 33 | const providers = params.providers; 34 | 35 | // here is the oauth code the client gave us 36 | const code = params.code; 37 | 38 | var providerName = params.provider || params.providerName; 39 | var provider = providers[providerName]; 40 | 41 | // 42 | // this is the body of our access_token request 43 | // 44 | var form = { 45 | client_id: provider.credentials.client_id, 46 | client_secret: provider.credentials.client_secret, 47 | code: code 48 | }; 49 | const params_token_endpoint_form = params.token_endpoint_form || {}; 50 | if (provider.token_endpoint_form) { 51 | for (var x in provider.token_endpoint_form) { 52 | form[x] = params_token_endpoint_form[x] || provider.token_endpoint_form[x]; 53 | } 54 | } 55 | 56 | // 57 | // form the request options for the access_token 58 | // 59 | var options = { 60 | url: provider.endpoints.token, 61 | method: 'POST', 62 | headers: { 63 | 'Content-Type': 'application/json' 64 | } 65 | }; 66 | if (provider.token_endpoint_form_is_json) { 67 | options.headers['Accept'] = 'application/json'; 68 | options.body = form; 69 | options.json = true; 70 | } else { 71 | options.form = form; 72 | } 73 | 74 | if (provider.token_endpoint_needs_auth) { 75 | options.auth = { 76 | user: form.client_id, 77 | pass: form.client_secret 78 | } 79 | } 80 | 81 | // 82 | // ok, here we go, exchanging the oauth code for an access_token 83 | // 84 | request(options, function (err, response, body) { 85 | 86 | if (err || response.statusCode >= 400) { 87 | const errorMessage = err || { statusCode: response.statusCode, body: body } 88 | console.error(JSON.stringify(errorMessage)); 89 | reject(errorMessage); 90 | 91 | } else { 92 | // 93 | // all right, we now have an access_token 94 | // 95 | if (typeof body == 'string') { 96 | body = JSON.parse(body); 97 | } 98 | 99 | // console.log("TOKEN RESPONSE", body) 100 | 101 | // 102 | // now we request the user's profile, so that we have some 103 | // persistent identifying information; e.g. email address 104 | // for account handle 105 | // 106 | request({ 107 | url: provider.endpoints.userinfo + (provider.token_as_query ? `?${provider.token_as_query}=${body.access_token}` : ''), 108 | method: 'GET', 109 | headers: { 110 | 'Accept': 'application/json', 111 | 'Authorization': (provider.authorization_type || 'token') + ' ' + body.access_token, 112 | 'User-Agent': 'OpenWhisk' 113 | } 114 | }, function (err2, response2, body2) { 115 | console.log(body2) 116 | if (err2 || response2.statusCode >= 400) { 117 | const errorMessage = err2 || { statusCode: response2.statusCode, body: body2 } 118 | console.error(JSON.stringify(errorMessage)); 119 | reject(errorMessage); 120 | 121 | } else { 122 | // 123 | // great, now we have the profile! 124 | // 125 | if (typeof body2 == 'string') { 126 | body2 = JSON.parse(body2); 127 | } 128 | 129 | resolve({ 130 | provider: providerName, 131 | access_token: body.access_token, 132 | refresh_token: body.refresh_token, 133 | id: body2[provider.userinfo_identifier], 134 | idRecord: provider.userinfo_identifier_full_record && body2, 135 | access_token_body: body, 136 | name: body2.name, 137 | given_name: body2.given_name, 138 | family_name: body2.given_name 139 | }); 140 | } 141 | }); 142 | } 143 | }); 144 | }); 145 | } 146 | 147 | -------------------------------------------------------------------------------- /openwhisk-oauth/oauth-redirect.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Copyright IBM Corp. 2017 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //------------------------------------------------------------------------------ 16 | 17 | function main(params) { 18 | //console.log(params) 19 | 20 | const providers = params.providers; 21 | var providerName = params.provider || params.providerName; 22 | var provider = providers[providerName]; 23 | 24 | let newLocation = provider.webapp_redirect + "?access_token=" + params.access_token 25 | + "&refresh_token=" + params.refresh_token 26 | + "&expires_in=" + params.access_token_body.expires_in 27 | + "&user_name=" + params.name 28 | 29 | return { 30 | headers: { location: newLocation }, 31 | statusCode: 302 32 | } 33 | } -------------------------------------------------------------------------------- /openwhisk-oauth/providers-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "google": { 3 | "endpoints": { 4 | "new_oauth_app": "https://console.developers.google.com/apis/credentials", 5 | "authorization": "https://accounts.google.com/o/oauth2/v2/auth", 6 | "token": "https://www.googleapis.com/oauth2/v4/token", 7 | "userinfo": "https://www.googleapis.com/oauth2/v3/userinfo" 8 | }, 9 | "authorization_type": "Bearer", 10 | "token_endpoint_form": { 11 | "grant_type": "authorization_code", 12 | "redirect_uri": "https://openwhisk.ng.bluemix.net/api/v1/web/niklas_heidloff@de.ibm.com_dev/default/oauth-login-and-redirect" 13 | }, 14 | "userinfo_identifier": "email", 15 | "credentials": { 16 | "client_id": "xxx", 17 | "client_secret": "xxx" 18 | }, 19 | "webapp_redirect": "http://localhost:4200/home" 20 | } 21 | } -------------------------------------------------------------------------------- /openwhisk-protected/init.sh: -------------------------------------------------------------------------------- 1 | export WSK="${WSK-wsk}" 2 | 3 | $WSK action update protected-action protected-action.js --kind nodejs:6 --web true 4 | 5 | $WSK api create --config-file my-api-swagger.json -------------------------------------------------------------------------------- /openwhisk-protected/my-api-swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0", 5 | "title": "MyAPI" 6 | }, 7 | "schemes": ["https"], 8 | "consumes": ["application/json"], 9 | "produces": ["application/json"], 10 | "paths": { 11 | "/action": { 12 | "get": { 13 | "operationId": "getAction", 14 | "x-openwhisk": { 15 | "namespace": "niklas_heidloff@de.ibm.com_dev", 16 | "action": "protected-action", 17 | "package": "default", 18 | "url": "https://openwhisk.ng.bluemix.net/api/v1/web/niklas_heidloff@de.ibm.com_dev/default/protected-action.json" 19 | }, 20 | "responses": { 21 | "200": { 22 | "description": "A successful invocation response" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | "x-ibm-configuration": { 29 | "assembly": { 30 | "execute": [{ 31 | "operation-switch": { 32 | "case": [{ 33 | "operations": ["getAction"], 34 | "execute": [{ 35 | "invoke": { 36 | "target-url": "https://openwhisk.ng.bluemix.net/api/v1/web/niklas_heidloff@de.ibm.com_dev/default/protected-action.json", 37 | "verb": "keep" 38 | } 39 | }] 40 | }], 41 | "otherwise": [], 42 | "title": "whisk-invoke" 43 | } 44 | }] 45 | }, 46 | "cors": { 47 | "enabled": true 48 | } 49 | }, 50 | "basePath": "/path", 51 | "securityDefinitions": { 52 | "google": { 53 | "type": "oauth2", 54 | "flow": "application", 55 | "tokenUrl": "", 56 | "x-tokenintrospect": { 57 | "url": "https://www.googleapis.com/oauth2/v3/tokeninfo" 58 | } 59 | } 60 | }, 61 | "security": [{ 62 | "$$label": "client_id, github", 63 | "google": [] 64 | }] 65 | } -------------------------------------------------------------------------------- /openwhisk-protected/protected-action.js: -------------------------------------------------------------------------------- 1 | 2 | function main(params) { 3 | return { message: 'Hello World ' + params.param1 }; 4 | } -------------------------------------------------------------------------------- /presentation/OpenWhisk.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/presentation/OpenWhisk.pdf -------------------------------------------------------------------------------- /screenshots/Oauth1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/Oauth1.png -------------------------------------------------------------------------------- /screenshots/Oauth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/Oauth2.png -------------------------------------------------------------------------------- /screenshots/Oauth3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/Oauth3.png -------------------------------------------------------------------------------- /screenshots/Oauth4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/Oauth4.png -------------------------------------------------------------------------------- /screenshots/Untitled Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/Untitled Diagram.png -------------------------------------------------------------------------------- /screenshots/google-api-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/google-api-manager.png -------------------------------------------------------------------------------- /screenshots/openwhisk-api-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/openwhisk-api-1.png -------------------------------------------------------------------------------- /screenshots/openwhisk-api-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/openwhisk-api-2.png -------------------------------------------------------------------------------- /screenshots/openwhisk-api-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/openwhisk-api-3.png -------------------------------------------------------------------------------- /screenshots/presentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/presentation.png -------------------------------------------------------------------------------- /screenshots/web-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/IBM-function-webapp/5423088f22d8ec35b234b32023a91461a3a43c6f/screenshots/web-app.png --------------------------------------------------------------------------------