├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── android ├── android.iml ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── io │ │ └── fullstack │ │ └── oauth │ │ ├── OAuthManagerConstants.java │ │ ├── OAuthManagerDialogFragment.java │ │ ├── OAuthManagerFragmentController.java │ │ ├── OAuthManagerModule.java │ │ ├── OAuthManagerOnAccessTokenListener.java │ │ ├── OAuthManagerPackage.java │ │ ├── OAuthManagerProviders.java │ │ ├── OAuthManagerStore.java │ │ └── services │ │ ├── ConfigurableApi.java │ │ └── SlackApi.java │ └── res │ └── layout │ └── webview_layout.xml ├── bin ├── cocoapods.sh └── prepare.sh ├── ios ├── OAuthManager.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── auser.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── auser.xcuserdatad │ │ └── xcschemes │ │ ├── OAuthManager.xcscheme │ │ └── xcschememanagement.plist ├── OAuthManager │ ├── OAuth1Client.h │ ├── OAuth1Client.m │ ├── OAuth2Client.h │ ├── OAuth2Client.m │ ├── OAuthClient.h │ ├── OAuthClient.m │ ├── OAuthClientProtocol.h │ ├── OAuthManager.h │ ├── OAuthManager.m │ ├── OAuthManagerConstants.h │ └── XMLReader │ │ ├── README.md │ │ ├── XMLReader.h │ │ └── XMLReader.m ├── Podfile.template └── buildScript.sh ├── lib ├── authProviders.js └── promisify.js ├── package-lock.json ├── package.json ├── react-native-oauth.js └── resources ├── capabilities.png ├── facebook ├── app.png ├── dev.facebook.png ├── facebook-redirect.png ├── redirect-url.png └── url-scheme.png ├── github └── apps.png ├── google ├── android-creds.png ├── auth-page.png ├── creds.png └── url-scheme.png ├── header-search-paths.png ├── info-panel.png ├── slack ├── create.png ├── creds.png ├── dev.png ├── getting_started.png └── redirect.png ├── twitter ├── api-key.png ├── app.png ├── callback-url.png └── url-scheme.png └── url-schemes.png /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator", "transform-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | node_modules 5 | npm-debug.log 6 | *.DS_Store 7 | 8 | # Xcode 9 | *.pbxuser 10 | *.mode1v3 11 | *.mode2v3 12 | *.perspectivev3 13 | *.xcuserstate 14 | project.xcworkspace/ 15 | xcuserdata/ 16 | 17 | .idea 18 | .vscode 19 | javac-services.0.log* 20 | dist/ 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | .babelrc 8 | .eslintrc 9 | npm-debug.log 10 | src/ 11 | examples/ 12 | public/ 13 | scripts/ 14 | test/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ari Lerner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-native-oauth 2 | 3 | The `react-native-oauth` library provides an interface to OAuth 1.0 and OAuth 2.0 providers with support for the following providers for React Native apps: 4 | 5 | * Twitter 6 | * Facebook 7 | * Google 8 | * Github 9 | * Slack 10 | 11 | ## TL;DR; 12 | 13 | This library cuts out the muck of dealing with the [OAuth 1.0](https://tools.ietf.org/html/rfc5849) and [OAuth 2.0](http://oauth.net/2/) protocols in react-native apps. The API is incredibly simple and straight-forward and is intended on getting you up and running quickly with OAuth providers (such as Facebook, Github, Twitter, etc). 14 | 15 | ```javascript 16 | import OAuthManager from 'react-native-oauth'; 17 | 18 | const manager = new OAuthManager('firestackexample') 19 | manager.configure({ 20 | twitter: { 21 | consumer_key: 'SOME_CONSUMER_KEY', 22 | consumer_secret: 'SOME_CONSUMER_SECRET' 23 | }, 24 | google: { 25 | callback_url: `io.fullstack.FirestackExample:/oauth2redirect`, 26 | client_id: 'YOUR_CLIENT_ID', 27 | client_secret: 'YOUR_SECRET' 28 | } 29 | }); 30 | 31 | // ... 32 | manager.authorize('google', {scopes: 'profile email'}) 33 | .then(resp => console.log('Your users ID')) 34 | .catch(err => console.log('There was an error')); 35 | ``` 36 | 37 | ### Help 38 | 39 | Due to other time contraints, I cannot continue to work on react-native-oauth for the time it deserves. If you're interested in supporting this library, please help! It's a widely used library and I'd love to continue supporting it. Looking for maintainers! 40 | 41 | ## Features 42 | 43 | * Isolates the OAuth experience to a few simple methods. 44 | * Atomatically stores the tokens for later retrieval 45 | * Works with many providers and simple to add new providers 46 | * Works on both Android and iOS 47 | * Makes calling API methods a snap 48 | * Integrates seamlessly with Firestack (but can be used without it) 49 | 50 | ## Installation 51 | 52 | Install `react-native-oauth` in the usual manner using `npm`: 53 | 54 | ```javascript 55 | npm install --save react-native-oauth 56 | ``` 57 | 58 | As we are integrating with react-native, we have a little more setup to integrating with our apps. 59 | 60 | ### iOS setup 61 | 62 | **Important**: This will _not_ work if you do not complete all the steps: 63 | 64 | - [ ] Link the `RCTLinkingManager` project 65 | - [ ] Update your `AppDelegate.h` file 66 | - [ ] Add KeychainSharing in your app 67 | - [ ] Link the `react-native-oauth` project with your application (`react-native link`) 68 | - [ ] Register a URL type of your application (Info tab -- see below) 69 | 70 | #### RCTLinkingManager 71 | 72 | Since `react-native-oauth` depends upon the `RCTLinkingManager` (from react-native core), we'll need to make sure we link this in our app. 73 | 74 | In your app, add the following line to your `HEADER SEARCH PATHS`: 75 | 76 | ``` 77 | $(SRCROOT)/../node_modules/react-native-oauth/ios/OAuthManager 78 | $(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS 79 | ``` 80 | 81 | ![](./resources/header-search-paths.png) 82 | 83 | Next, navigate to the neighboring "Build Phases" section of project settings, find the "Link Binary with Library" drop down, expand it, and click the "+" to add _libOAuthManager.a_ to the list. 84 | 85 | Make sure to Update your `AppDelegate.m` as below, otherwise it will _not_ work. 86 | 87 | #### Automatically with [rnpm](https://github.com/rnpm/rnpm) 88 | 89 | To automatically link our `react-native-oauth` client to our application, use the `rnpm` tool. [rnpm](https://github.com/rnpm/rnpm) is a React Native package manager which can help to automate the process of linking package environments. 90 | 91 | ```bash 92 | react-native link react-native-oauth 93 | ``` 94 | 95 | Note: due to some restrictions on iOS, this module requires you to install cocoapods. The process has been semi-automated through using the above `react-native link` command. 96 | 97 | Once you have linked this library, run the following command in the root directory: 98 | 99 | ``` 100 | (cd ios && pod install) 101 | ``` 102 | 103 | Open in xcode the created `.xcworkspace` in the `ios/` directory (**NOT THE `.xproj` file**) when it's complete. 104 | 105 | When working on iOS 10, we'll need to enable _Keychain Sharing Entitlement_ in _Capabilities_ of the build target of `io.fullstack.oauth.AUTH_MANAGER`. 106 | 107 | ![](./resources/capabilities.png) 108 | 109 | ## Handle deep linking loading 110 | 111 | **Required step** 112 | 113 | We'll need to handle app loading from a url with our app in order to handle authentication from other providers. That is, we'll need to make sure our app knows about the credentials we're authenticating our users against when the app loads _after_ a provider is authenticated against. 114 | 115 | ### iOS setup 116 | 117 | We need to add a callback method in our `ios/AppDelegate.m` file and then call our OAuthManager helper method. Let's load the `ios/AppDelegate.m` file and add the following all the way at the bottom (but before the `@end`): 118 | 119 | ```objectivec 120 | // Add the import at the top: 121 | #import "OAuthManager.h" 122 | // ... 123 | @implementation AppDelegate 124 | // ... 125 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { 126 | return [OAuthManager handleOpenUrl:application 127 | openURL:url 128 | sourceApplication:sourceApplication 129 | annotation:annotation]; 130 | } 131 | ``` 132 | 133 | In addition, we'll need to set up the handlers within the iOS app. Add the following line somewhere in your `application:didFinishLaunchingWithOptions:` method, like so: 134 | 135 | ```objectivec 136 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 137 | { 138 | NSURL *jsCodeLocation; 139 | 140 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 141 | 142 | // other existing setup here 143 | 144 | // ADD THIS LINE SOMEWHERE IN THIS FUNCTION 145 | [OAuthManager setupOAuthHandler:application]; 146 | // ... 147 | 148 | [self.window makeKeyAndVisible]; 149 | return YES; 150 | } 151 | ``` 152 | 153 | When our app loads up with a request that is coming back from OAuthManager _and_ matches the url pattern, OAuthManager will take over and handle the rest and storing the credentials for later use. 154 | 155 | ### Adding URL schemes 156 | 157 | In order for our app to load through these callbacks, we need to tell our iOS app that we want to load them. In order to do that, we'll have to create some URL schemes to register our app. Some providers require specific schemes (mentioned later). 158 | 159 | These URL schemes can be added by navigating to to the `info` panel of our app in Xcode (see screenshot). 160 | 161 | ![](./resources/info-panel.png) 162 | 163 | Let's add the appropriate one for our provider. For instance, to set up twitter, add the app name as a URL scheme in the URL scheme box. 164 | 165 | ![](./resources/url-schemes.png) 166 | 167 | ### Android setup 168 | 169 | After we link `react-native-oauth` to our application, we're ready to go. Android integration is much simpler, thanks to the in-app browser ability for our apps. `react-native-oauth` handles this for you. 170 | 171 | One note, *all* of the callback urls follow the scheme: `http://localhost/[provider_name]`. Make sure this is set as a configuration for each provider below (documented in the provider setup sections). 172 | 173 | Make sure you add the following to your `android/build.gradle` file: 174 | 175 | ``` 176 | maven { url "https://jitpack.io" } 177 | ``` 178 | 179 | For instance, an example `android/build.gradle` file would look like this: 180 | 181 | ``` 182 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 183 | 184 | buildscript { 185 | // ... 186 | } 187 | 188 | allprojects { 189 | repositories { 190 | mavenLocal() 191 | jcenter() 192 | maven { url "https://jitpack.io" } // <~ ADD THIS LINE 193 | maven { 194 | url "$rootDir/../node_modules/react-native/android" 195 | } 196 | } 197 | } 198 | ``` 199 | 200 | ## Creating the manager 201 | 202 | In our JS, we can create the manager by instantiating a new instance of it using the `new` method and passing it the name of our app: 203 | 204 | ```javascript 205 | const manager = new OAuthManager('firestackexample') 206 | ``` 207 | 208 | We need to pass the name of our app as the oauth manager uses this to create callback keys. This _must_ match the URL route created in your iOS app. For instance, above we created a URL scheme for Twitter. Pass this as the string in the `OAuthManager` constructor. 209 | 210 | ## Configuring our providers 211 | 212 | Providers, such as Facebook require some custom setup for each one. The following providers have been implemented and we're working on making more (and making it easier to add more, although the code is not impressively complex either, so it should be relatively simple to add more providers). 213 | 214 | In order to configure providers, the `react-native-oauth` library exports the `configureProvider()` method, which accepts _two_ parameters and returns a promise: 215 | 216 | 1. The provider name, such as `twitter` and `facebook` 217 | 2. The provider's individual credentials 218 | 219 | For instance, this might look like: 220 | 221 | ```javascript 222 | const config = { 223 | twitter: { 224 | consumer_key: 'SOME_CONSUMER_KEY', 225 | consumer_secret: 'SOME_CONSUMER_SECRET' 226 | }, 227 | facebook: { 228 | client_id: 'YOUR_CLIENT_ID', 229 | client_secret: 'YOUR_CLIENT_SECRET' 230 | } 231 | } 232 | // Create the manager 233 | const manager = new OAuthManager('firestackexample') 234 | // configure the manager 235 | manager.configure(config); 236 | ``` 237 | 238 | The `consumer_key` and `consumer_secret` values are _generally_ provided by the provider development program. In the case of [twitter](https://apps.twitter.com), we can create an app and generate these values through their [development dashboard](https://apps.twitter.com). 239 | 240 | ### Implemented providers 241 | 242 | The following list are the providers we've implemented thus far in `react-native-oauth` and the _required_ keys to pass when configuring the provider: 243 | 244 | #### Twitter (iOS/Android) 245 | 246 | To authenticate against twitter, we need to register a Twitter application. Register your twitter application (or create a new one at [apps.twitter.com](https://apps.twitter.com)). 247 | 248 | ![](./resources/twitter/app.png) 249 | 250 | Once you have created one, navigate to the application and find the `Keys and Access Tokens`. Take note of the consumer key and secret: 251 | 252 | ![](./resources/twitter/api-key.png) 253 | 254 | For the authentication to work properly, you need to set the Callback URL. It doesn't matter what you choose as long as its a valid url. 255 | 256 | ![](./resources/twitter/callback-url.png) 257 | 258 | Twitter's URL scheme needs to be the app name (that we pass into the constructor method). Make sure we have one registered in Xcode as the same name: 259 | 260 | ![](./resources/twitter/url-scheme.png) 261 | 262 | Add these values to the authorization configuration to pass to the `configure()` method as: 263 | 264 | ```javascript 265 | const config = { 266 | twitter: { 267 | consumer_key: 'SOME_CONSUMER_KEY', 268 | consumer_secret: 'SOME_CONSUMER_SECRET' 269 | } 270 | } 271 | ``` 272 | 273 | #### Facebook (iOS/Android) 274 | 275 | To add facebook authentication, we'll need to have a Facebook app. To create one (or use an existing one), navigate to [developers.facebook.com/](https://developers.facebook.com/). 276 | 277 | ![](./resources/facebook/dev.facebook.png) 278 | 279 | Find or create an application and find the app id. Take note of this app id. Next, navigate to the `Settings` panel and find your client_secret. 280 | 281 | ![](./resources/facebook/app.png) 282 | 283 | Before we leave the Facebook settings, we need to tell Facebook we have a new redirect url to register. Navigate to the bottom of the page and add the following into the `bundle ID` field: 284 | 285 | `fb{YOUR_APP_ID}` 286 | 287 | For instance, my app ID in this example is: `1745641015707619`. In the `Bundle ID` field, I have added `fb1745641015707619`. 288 | 289 | ![](./resources/facebook/redirect-url.png) 290 | 291 | For Android, you will also need to set the redirect url to `http://localhost/facebook` in the Facebook Login settings. 292 | 293 | ![](./resources/facebook/redirect-url.png) 294 | 295 | We'll need to create a new URL scheme for Facebook and (this is a weird bug on the Facebook side) the facebook redirect URL scheme _must be the first one_ in the list. The URL scheme needs to be the same id as the `Bundle ID` copied from above: 296 | 297 | ![](./resources/facebook/facebook-redirect.png) 298 | 299 | Back in our application, add the App ID and the secret as: 300 | 301 | ```javascript 302 | const config = { 303 | facebook: { 304 | client_id: 'YOUR_APP_ID', 305 | client_secret: 'YOUR_APP_SECRET' 306 | } 307 | } 308 | ``` 309 | 310 | #### Google (iOS) 311 | 312 | To add Google auth to our application, first we'll need to create a google application. Create or use an existing one by heading to the [developers.google.com/](https://developers.google.com/) page (or the console directly at [https://console.developers.google.com](https://console.developers.google.com)). 313 | 314 | ![](./resources/google/auth-page.png) 315 | 316 | We need to enable the `Identity Toolkit API` API. Click on `Enable API` and add this api to your app. Once it's enabled, we'll need to collect our credentials. 317 | 318 | Navigate to the `Credentials` tab and create a new credential. Create an **iOS API credential**. Take note of the `client_id` and the `iOS URL scheme`. In addition, make sure to set the bundle ID as the bundle id in our application in Xcode: 319 | 320 | ![](./resources/google/creds.png) 321 | 322 | Take note of the `iOS URL Scheme`. We'll need to add this as a URL scheme in our app. In the `Info` panel of our app target (in Xcode), add the URL scheme: 323 | 324 | ![](./resources/google/url-scheme.png) 325 | 326 | Finally, add the `client_id` credential as the id from the url page as well as the ios scheme (with any path) in our app configuration: 327 | 328 | ```javascript 329 | const config = { 330 | google: { 331 | callback_url: `[IOS SCHEME]:/google`, 332 | client_id: 'YOUR_CLIENT_ID' 333 | } 334 | } 335 | ``` 336 | 337 | #### Google (Android) 338 | 339 | To set up Google on Android, follow the same steps as before, except this time instead of creating an iOS API, create a **web api credential**. Make sure to add the **redirect url** at the bottom (it defaults to `http://localhost/google`): 340 | 341 | ![](./resources/google/android-creds.png) 342 | 343 | When creating an Android-specific configuration, create a file called `config/development.android.js`. React Native will load it instead of the `config/development.js` file automatically on Android. 344 | 345 | #### Github (iOS/Android) 346 | 347 | Adding Github auth to our application is pretty simple as well. We'll need to create a web application on the github apps page, which can be found at [https://github.com/settings/developers](https://github.com/settings/developers). Create one, making sure to add _two_ apps (one for iOS and one for Android) with the callback urls as: 348 | 349 | * ios: [app_name]:// oauth (for example: `firestackexample://oauth`) 350 | * android: http://localhost/github 351 | 352 | Take note of the `client_id` and `client_secret` 353 | 354 | ![](./resources/github/apps.png) 355 | 356 | The `iOS URL Scheme` is the same as the twitter version, which means we'll just add the app name as a URL scheme (i.e. `firestackexample`). 357 | 358 | Add the `client_id` and `client_secret` credentials to your configuration object: 359 | 360 | ```javascript 361 | const config = { 362 | github: { 363 | client_id: 'YOUR_CLIENT_ID', 364 | client_secret: 'YOUR_CLIENT_SECRET' 365 | } 366 | } 367 | ``` 368 | 369 | ## Slack 370 | 371 | We'll need to create an app first. Head to the slack developer docs at [https://slack.com/developers](https://slack.com/developers). 372 | 373 | ![](./resources/slack/dev.png) 374 | 375 | Click on the Getting Started button: 376 | 377 | ![](./resources/slack/getting_started.png) 378 | 379 | From here, find the `create an app` link: 380 | 381 | ![](./resources/slack/create.png) 382 | 383 | Take note of the `client_id` and the `client_secret`. We'll place these in our configuration object just like so: 384 | 385 | ```javascript 386 | const config = { 387 | slack: { 388 | client_id: 'YOUR_CLIENT_ID', 389 | client_secret: 'YOUR_CLIENT_SECRET' 390 | } 391 | } 392 | ``` 393 | 394 | Lastly, Slack requires us to add a redirect_url. 395 | 396 | For **iOS**: the callback_url pattern is `${app_name}://oauth`, so make sure to add your redirect_url where it asks for them before starting to work with the API. 397 | 398 | for **Android**: the `callback_url` pattern is `http://localhost/slack`. Be sure to add this to your list of redirect urls. 399 | 400 | ![](./resources/slack/redirect.png) 401 | 402 | ## Authenticating against our providers 403 | 404 | We can use the manager in our app using the `authorize()` method on the manager. 405 | 406 | The `authorize` method takes two arguments (the first one is required): 407 | 408 | * The provider we wish to authenticate against (i.e. twitter, facebook) 409 | * The list of options on a per-provider basis (optional) 410 | 411 | For example: 412 | 413 | ```javascript 414 | manager.authorize('twitter') 415 | .then(resp => console.log(resp)) 416 | .catch(err => console.log(err)); 417 | ``` 418 | 419 | This method returns a promise that is resolved once the authentication has been completed. You'll get access to the authentication keys in the `resp` object. 420 | 421 | The `resp` object is set as follows: 422 | 423 | ```javascript 424 | { 425 | status: "ok", 426 | response: { 427 | authorized: true, (boolean) 428 | uuid: "UUID", (user UUID) 429 | credentials: { 430 | access_token: "access token", 431 | refresh_token: "refresh token", 432 | type: 1 433 | } 434 | } 435 | } 436 | ``` 437 | 438 | The second argument accepts an object where we can ask for additional scopes, override default values, etc. 439 | 440 | ```javascript 441 | manager.authorize('google', {scopes: 'email,profile'}) 442 | .then(resp => console.log(resp)) 443 | .catch(err => console.log(err)); 444 | ``` 445 | 446 | * Scopes are a list of scopes _comma separated_ as a string. 447 | 448 | ## Calling a provider's API 449 | 450 | We can use OAuthManager to make requests to endpoints from our providers as well. For instance, let's say we want to get a user's time line from twitter. We would make the request to the url [https://api.twitter.com/1.1/statuses/user_timeline.json](https://api.twitter.com/1.1/statuses/user_timeline.json) 451 | 452 | If our user has been authorized for thi request, we can execute the request using the credentials stored by the OAuthManager. 453 | 454 | The `makeRequest()` method accepts 3 parameters: 455 | 456 | 1. The provider we're making a request to 457 | 2. The url (or path) we want to make the request 458 | 3. Any additional options 459 | 460 | We can pass a list of options for our request with the last argument. The keys OAuthManager recognizes are: 461 | 462 | 1. `params` - The query parameters 463 | 2. `method` - The http method to make the request with. 464 | 465 | Available HTTP methods: 466 | * get 467 | * post 468 | * put 469 | * delete 470 | * head 471 | * options 472 | * trace 473 | 474 | 475 | ```javascript 476 | const userTimelineUrl = 'https://api.twitter.com/1.1/statuses/user_timeline.json'; 477 | manager 478 | .makeRequest('twitter', userTimelineUrl) 479 | .then(resp => { 480 | console.log('Data ->', resp.data); 481 | }); 482 | ``` 483 | 484 | "me" represents the authenticated user, in any call to the Google+ API 485 | 486 | ```javascript 487 | const googleUrl = 'https://www.googleapis.com/plus/v1/people/me'; 488 | manager 489 | .makeRequest('google', googleUrl) 490 | .then(resp => { 491 | console.log('Data -> ', resp.data); 492 | }); 493 | 494 | ``` 495 | 496 | It's possible to use just the path as well. For instance, making a request with Facebook at the `/me` endpoint can be: 497 | 498 | ```javascript 499 | manager 500 | .makeRequest('facebook', '/me') 501 | .then(resp => { 502 | console.log('Data ->', resp.data); 503 | }); 504 | ``` 505 | 506 | To add more data to our requests, we can pass a third argument: 507 | 508 | ```javascript 509 | manager 510 | .makeRequest('facebook', '/me', { 511 | headers: { 'Content-Type': 'application/java' }, 512 | params: { email: 'me+rocks@ari.io' } 513 | }) 514 | .then(resp => { 515 | console.log('Data ->', resp.data); 516 | }); 517 | ``` 518 | 519 | ## Getting authorized accounts 520 | 521 | Since OAuthManager handles storing user accounts, we can query it to see which accounts have already been authorized or not using `savedAccounts()`: 522 | 523 | ```javascript 524 | manager.savedAccounts() 525 | .then(resp => { 526 | console.log('account list: ', resp.accounts); 527 | }) 528 | ``` 529 | 530 | ## deauthorize() 531 | 532 | We can `deauthorize()` our user's from using the provider by calling the `deauthorize()` method. It accepts a single parameter: 533 | 534 | 1. The `provider` we want to remove from our user credentials. 535 | 536 | ```javascript 537 | manager.deauthorize('twitter'); 538 | ``` 539 | 540 | ## Adding your own providers 541 | 542 | To add your own providers you can use the `addProvider()` method and fill in your provider details: 543 | 544 | ```javascript 545 | manager.addProvider({ 546 | 'name_of_provider': { 547 | auth_version: '2.0', 548 | authorize_url: 'https://provider.dev/oauth', 549 | access_token_url: 'https://provider.dev/oauth/token', 550 | callback_url: ({app_name}) => `${app_name}://oauth`, 551 | } 552 | }); 553 | ``` 554 | 555 | ## Contributing 556 | 557 | This is _open-source_ software and we can make it rock for everyone through contributions. 558 | 559 | ```shell 560 | git clone https://github.com/fullstackreact/react-native-oauth.git 561 | cd react-native-oauth 562 | npm install 563 | ``` 564 | ___ 565 | 566 | ## TODOS: 567 | 568 | * [x] Simplify method of adding providers 569 | * [x] Add github(https://developer.github.com/v3/oauth/) support 570 | * [x] Add Google support 571 | * [x] Add Facebook support 572 | * [x] Add Android support 573 | -------------------------------------------------------------------------------- /android/android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | maven { url "https://jitpack.io" } 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.1.3' 8 | } 9 | } 10 | // END 11 | 12 | apply plugin: 'com.android.library' 13 | apply plugin: 'maven' 14 | 15 | android { 16 | compileSdkVersion 23 17 | buildToolsVersion "23.0.1" 18 | 19 | defaultConfig { 20 | minSdkVersion 16 21 | targetSdkVersion 23 22 | versionCode 1 23 | versionName "1.0" 24 | multiDexEnabled true 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | } 30 | } 31 | 32 | android.packagingOptions { 33 | exclude 'META-INF/LICENSE' 34 | } 35 | } 36 | 37 | allprojects { 38 | repositories { 39 | jcenter() 40 | maven { url "https://jitpack.io" } 41 | } 42 | } 43 | // END 44 | 45 | dependencies { 46 | compile 'com.facebook.react:react-native:+' 47 | compile 'com.github.scribejava:scribejava-apis:3.4.1' 48 | compile 'com.github.delight-im:Android-AdvancedWebView:v3.0.0' 49 | compile 'com.google.code.gson:gson:+' 50 | } 51 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 02 21:23:30 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Tue Apr 11 11:36:49 IST 2017 11 | sdk.dir=/Users/divyanshunegi/Downloads/adt-bundle-mac-x86_64-20140321/sdk 12 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerConstants.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | public interface OAuthManagerConstants { 4 | String CREDENTIALS_STORE_PREF_FILE = "oauth_manager"; 5 | } -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerDialogFragment.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Dialog; 5 | import android.app.DialogFragment; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.res.Resources; 9 | import android.graphics.Bitmap; 10 | import android.graphics.Color; 11 | import android.graphics.drawable.ColorDrawable; 12 | import android.net.Uri; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.text.TextUtils; 16 | import android.util.DisplayMetrics; 17 | import android.util.Log; 18 | import android.view.Display; 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.view.ViewGroup.LayoutParams; 23 | import android.view.Window; 24 | import android.view.WindowManager; 25 | import android.webkit.WebView; 26 | import android.webkit.WebViewClient; 27 | import android.widget.ProgressBar; 28 | import android.widget.RelativeLayout; 29 | 30 | import com.facebook.react.bridge.ReactContext; 31 | import com.github.scribejava.core.model.OAuth1AccessToken; 32 | 33 | import java.lang.reflect.Method; 34 | import java.util.Set; 35 | 36 | import im.delight.android.webview.AdvancedWebView; 37 | 38 | import java.util.regex.Matcher; 39 | import java.util.regex.Pattern; 40 | 41 | public class OAuthManagerDialogFragment extends DialogFragment implements AdvancedWebView.Listener { 42 | 43 | private static final int WEBVIEW_TAG = 100001; 44 | private static final int WIDGET_TAG = 100002; 45 | 46 | private static final String TAG = "OauthFragment"; 47 | private OAuthManagerFragmentController mController; 48 | 49 | private ReactContext mReactContext; 50 | private AdvancedWebView mWebView; 51 | private ProgressBar mProgressBar; 52 | 53 | public static final OAuthManagerDialogFragment newInstance( 54 | final ReactContext reactContext, 55 | OAuthManagerFragmentController controller 56 | ) { 57 | Bundle args = new Bundle(); 58 | OAuthManagerDialogFragment frag = 59 | new OAuthManagerDialogFragment(reactContext, controller); 60 | return frag; 61 | } 62 | 63 | public OAuthManagerDialogFragment( 64 | final ReactContext reactContext, 65 | OAuthManagerFragmentController controller 66 | ) { 67 | this.mController = controller; 68 | this.mReactContext = reactContext; 69 | } 70 | 71 | @Override 72 | public Dialog onCreateDialog(Bundle savedInstanceState) { 73 | Dialog dialog = super.onCreateDialog(savedInstanceState); 74 | dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 75 | return dialog; 76 | } 77 | 78 | @Override 79 | public void onStart() { 80 | super.onStart(); 81 | Dialog dialog = getDialog(); 82 | if (dialog != null) { 83 | dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 84 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 85 | } 86 | } 87 | 88 | @Override 89 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 90 | final Context context = mReactContext; 91 | LayoutParams rootViewLayoutParams = this.getFullscreenLayoutParams(context); 92 | 93 | RelativeLayout rootView = new RelativeLayout(context); 94 | 95 | mProgressBar = new ProgressBar(context); 96 | RelativeLayout.LayoutParams progressParams = new RelativeLayout.LayoutParams(convertDpToPixel(50f,context),convertDpToPixel(50f,context)); 97 | progressParams.addRule(RelativeLayout.CENTER_IN_PARENT); 98 | mProgressBar.setLayoutParams(progressParams); 99 | mProgressBar.setIndeterminate(true); 100 | 101 | getDialog().setCanceledOnTouchOutside(true); 102 | rootView.setLayoutParams(rootViewLayoutParams); 103 | 104 | // mWebView = (AdvancedWebView) rootView.findViewById(R.id.webview); 105 | Log.d(TAG, "Creating webview"); 106 | mWebView = new AdvancedWebView(context); 107 | // mWebView.setId(WEBVIEW_TAG); 108 | mWebView.setListener(this, this); 109 | mWebView.setVisibility(View.VISIBLE); 110 | mWebView.getSettings().setJavaScriptEnabled(true); 111 | mWebView.getSettings().setDomStorageEnabled(true); 112 | 113 | 114 | LayoutParams layoutParams = this.getFullscreenLayoutParams(context); 115 | //new LayoutParams( 116 | // LayoutParams.FILL_PARENT, 117 | // DIALOG_HEIGHT 118 | // ); 119 | // mWebView.setLayoutParams(layoutParams); 120 | 121 | rootView.addView(mWebView, layoutParams); 122 | rootView.addView(mProgressBar,progressParams); 123 | 124 | // LinearLayout pframe = new LinearLayout(context); 125 | // pframe.setId(WIDGET_TAG); 126 | // pframe.setOrientation(LinearLayout.VERTICAL); 127 | // pframe.setVisibility(View.GONE); 128 | // pframe.setGravity(Gravity.CENTER); 129 | // pframe.setLayoutParams(layoutParams); 130 | 131 | // rootView.addView(pframe, layoutParams); 132 | 133 | this.setupWebView(mWebView); 134 | mController.getRequestTokenUrlAndLoad(mWebView); 135 | 136 | Log.d(TAG, "Loading view..."); 137 | return rootView; 138 | } 139 | 140 | private LayoutParams getFullscreenLayoutParams(Context context) { 141 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 142 | // DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 143 | Display display = wm.getDefaultDisplay(); 144 | int realWidth; 145 | int realHeight; 146 | 147 | if (Build.VERSION.SDK_INT >= 17){ 148 | //new pleasant way to get real metrics 149 | DisplayMetrics realMetrics = new DisplayMetrics(); 150 | display.getRealMetrics(realMetrics); 151 | realWidth = realMetrics.widthPixels; 152 | realHeight = realMetrics.heightPixels; 153 | 154 | } else if (Build.VERSION.SDK_INT >= 14) { 155 | //reflection for this weird in-between time 156 | try { 157 | Method mGetRawH = Display.class.getMethod("getRawHeight"); 158 | Method mGetRawW = Display.class.getMethod("getRawWidth"); 159 | realWidth = (Integer) mGetRawW.invoke(display); 160 | realHeight = (Integer) mGetRawH.invoke(display); 161 | } catch (Exception e) { 162 | //this may not be 100% accurate, but it's all we've got 163 | realWidth = display.getWidth(); 164 | realHeight = display.getHeight(); 165 | Log.e("Display Info", "Couldn't use reflection to get the real display metrics."); 166 | } 167 | 168 | } else { 169 | //This should be close, as lower API devices should not have window navigation bars 170 | realWidth = display.getWidth(); 171 | realHeight = display.getHeight(); 172 | } 173 | 174 | return new LayoutParams(realWidth, realHeight); 175 | } 176 | 177 | 178 | private void setupWebView(AdvancedWebView webView) { 179 | webView.setWebViewClient(new WebViewClient() { 180 | @Override 181 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 182 | return interceptUrl(view, url, true); 183 | } 184 | 185 | @Override 186 | public void onPageFinished(WebView view, String url) { 187 | super.onPageFinished(view, url); 188 | mProgressBar.setVisibility(View.GONE); 189 | } 190 | 191 | @Override 192 | public void onReceivedError(WebView view, int code, String desc, String failingUrl) { 193 | Log.i(TAG, "onReceivedError: " + failingUrl); 194 | super.onReceivedError(view, code, desc, failingUrl); 195 | onError(desc); 196 | } 197 | 198 | private boolean interceptUrl(WebView view, String url, boolean loadUrl) { 199 | Log.i(TAG, "interceptUrl called with url: " + url); 200 | 201 | // url would be http://localhost/twitter?denied=xxx when it's canceled 202 | Pattern p = Pattern.compile("\\S*denied\\S*"); 203 | Matcher m = p.matcher(url); 204 | if(m.matches()){ 205 | Log.i(TAG, "authentication is canceled"); 206 | return false; 207 | } 208 | 209 | if (isCallbackUri(url, mController.getCallbackUrl())) { 210 | mController.getAccessToken(mWebView, url); 211 | return true; 212 | } 213 | 214 | if (loadUrl) { 215 | view.loadUrl(url); 216 | } 217 | 218 | return false; 219 | } 220 | }); 221 | } 222 | 223 | public void setComplete(final OAuth1AccessToken accessToken) { 224 | Log.d(TAG, "Completed: " + accessToken); 225 | } 226 | 227 | 228 | // @Override 229 | // public void onDismiss(final DialogInterface dialog) { 230 | // super.onDismiss(dialog); 231 | // Log.d(TAG, "Dismissing dialog"); 232 | // } 233 | 234 | 235 | // @Override 236 | // void onCancel(DialogInterface dialog) { 237 | // Log.d(TAG, "onCancel called for dialog"); 238 | // onError("Cancelled"); 239 | // } 240 | 241 | @SuppressLint("NewApi") 242 | @Override 243 | public void onResume() { 244 | super.onResume(); 245 | mWebView.onResume(); 246 | Log.d(TAG, "onResume called"); 247 | } 248 | 249 | @SuppressLint("NewApi") 250 | @Override 251 | public void onPause() { 252 | Log.d(TAG, "onPause called"); 253 | mWebView.onPause(); 254 | super.onPause(); 255 | } 256 | 257 | @Override 258 | public void onDestroy() { 259 | mWebView.onDestroy(); 260 | this.mController = null; 261 | // ... 262 | super.onDestroy(); 263 | } 264 | 265 | @Override 266 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 267 | super.onActivityResult(requestCode, resultCode, intent); 268 | mWebView.onActivityResult(requestCode, resultCode, intent); 269 | 270 | Log.d(TAG, "onActivityResult: " + requestCode); 271 | // ... 272 | } 273 | 274 | @Override 275 | public void onPageStarted(String url, Bitmap favicon) { 276 | Log.d(TAG, "onPageStarted " + url); 277 | } 278 | 279 | @Override 280 | public void onPageFinished(String url) { 281 | Log.d(TAG, "onPageFinished: " + url); 282 | // mController.onComplete(url); 283 | } 284 | 285 | @Override 286 | public void onPageError(int errorCode, String description, String failingUrl) { 287 | Log.e(TAG, "onPageError: " + failingUrl); 288 | mController.onError(errorCode, description, failingUrl); 289 | } 290 | 291 | @Override 292 | public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { } 293 | 294 | @Override 295 | public void onExternalPageRequest(String url) { 296 | Log.d(TAG, "onExternalPageRequest: " + url); 297 | } 298 | 299 | private void onError(String msg) { 300 | Log.e(TAG, "Error: " + msg); 301 | } 302 | 303 | static boolean isCallbackUri(String uri, String callbackUrl) { 304 | Uri u = null; 305 | Uri r = null; 306 | try { 307 | u = Uri.parse(uri); 308 | r = Uri.parse(callbackUrl); 309 | } catch (NullPointerException e) { 310 | return false; 311 | } 312 | 313 | if (u == null || r == null) return false; 314 | 315 | boolean rOpaque = r.isOpaque(); 316 | boolean uOpaque = u.isOpaque(); 317 | if (uOpaque != rOpaque) return false; 318 | 319 | if (rOpaque) return TextUtils.equals(uri, callbackUrl); 320 | if (!TextUtils.equals(r.getScheme(), u.getScheme())) return false; 321 | if (u.getPort() != r.getPort()) return false; 322 | if (!TextUtils.isEmpty(r.getPath()) && !TextUtils.equals(r.getPath(), u.getPath())) return false; 323 | 324 | Set paramKeys = r.getQueryParameterNames(); 325 | for (String key : paramKeys) { 326 | if (!TextUtils.equals(r.getQueryParameter(key), u.getQueryParameter(key))) return false; 327 | } 328 | 329 | String frag = r.getFragment(); 330 | if (!TextUtils.isEmpty(frag) && !TextUtils.equals(frag, u.getFragment())) return false; 331 | return true; 332 | } 333 | 334 | public static int convertDpToPixel(float dp, Context context){ 335 | Resources resources = context.getResources(); 336 | DisplayMetrics metrics = resources.getDisplayMetrics(); 337 | float px = dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); 338 | return (int)px; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerFragmentController.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import android.app.Fragment; 4 | import android.app.FragmentTransaction; 5 | import android.net.Uri; 6 | import android.os.AsyncTask; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.util.Log; 10 | 11 | import com.facebook.react.bridge.ReactContext; 12 | import com.github.scribejava.core.exceptions.OAuthConnectionException; 13 | import com.github.scribejava.core.model.OAuth1AccessToken; 14 | import com.github.scribejava.core.model.OAuth1RequestToken; 15 | import com.github.scribejava.core.model.OAuth2AccessToken; 16 | import com.github.scribejava.core.oauth.OAuth10aService; 17 | import com.github.scribejava.core.oauth.OAuth20Service; 18 | 19 | import java.io.IOException; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | import im.delight.android.webview.AdvancedWebView; 24 | 25 | // Credit where credit is due: 26 | // Mostly taken from 27 | // https://github.com/wuman/android-oauth-client/blob/6e01b81b7319a6954a1156e8b93c0b5cbeb61446/library/src/main/java/com/wuman/android/auth/DialogFragmentController.java 28 | 29 | public class OAuthManagerFragmentController { 30 | private static final String TAG = "OAuthManager"; 31 | 32 | private final android.app.FragmentManager fragmentManager; 33 | private final Handler uiHandler; 34 | 35 | private ReactContext context; 36 | private String providerName; 37 | private String authVersion; 38 | private OAuth10aService oauth10aService; 39 | private OAuth20Service oauth20Service; 40 | private String callbackUrl; 41 | private OAuth1RequestToken oauth1RequestToken; 42 | private HashMap mCfg; 43 | private AdvancedWebView mWebView; 44 | 45 | private Runnable onAccessToken; 46 | private OAuthManagerOnAccessTokenListener mListener; 47 | 48 | private void runOnMainThread(Runnable runnable) { 49 | uiHandler.post(runnable); 50 | } 51 | 52 | public OAuthManagerFragmentController( 53 | final ReactContext mReactContext, 54 | android.app.FragmentManager fragmentManager, 55 | final String providerName, 56 | OAuth10aService oauthService, 57 | final String callbackUrl 58 | ) { 59 | this.uiHandler = new Handler(Looper.getMainLooper()); 60 | this.fragmentManager = fragmentManager; 61 | 62 | this.context = mReactContext; 63 | this.providerName = providerName; 64 | this.authVersion = "1.0"; 65 | this.oauth10aService = oauthService; 66 | this.callbackUrl = callbackUrl; 67 | } 68 | 69 | public OAuthManagerFragmentController( 70 | final ReactContext mReactContext, 71 | android.app.FragmentManager fragmentManager, 72 | final String providerName, 73 | OAuth20Service oauthService, 74 | final String callbackUrl 75 | ) { 76 | this.uiHandler = new Handler(Looper.getMainLooper()); 77 | this.fragmentManager = fragmentManager; 78 | 79 | this.context = mReactContext; 80 | this.providerName = providerName; 81 | this.authVersion = "2.0"; 82 | this.oauth20Service = oauthService; 83 | this.callbackUrl = callbackUrl; 84 | } 85 | 86 | 87 | public void requestAuth(HashMap cfg, OAuthManagerOnAccessTokenListener listener) { 88 | mListener = listener; 89 | mCfg = cfg; 90 | 91 | runOnMainThread(new Runnable() { 92 | @Override 93 | public void run() { 94 | Log.d(TAG, "fragment manager checking..."); 95 | if (fragmentManager.isDestroyed()) { 96 | return; 97 | } 98 | 99 | FragmentTransaction ft = fragmentManager.beginTransaction(); 100 | Fragment prevDialog = 101 | fragmentManager.findFragmentByTag(TAG); 102 | 103 | Log.d(TAG, "previous() Dialog?"); 104 | 105 | if (prevDialog != null) { 106 | ft.remove(prevDialog); 107 | } 108 | 109 | Log.d(TAG, "Creating new Fragment"); 110 | OAuthManagerDialogFragment frag = 111 | OAuthManagerDialogFragment.newInstance(context, OAuthManagerFragmentController.this); 112 | 113 | ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 114 | ft.add(frag, TAG); 115 | Log.d(TAG, "Committing with State Loss"); 116 | // ft.commit(); 117 | ft.commitAllowingStateLoss(); 118 | } 119 | }); 120 | } 121 | 122 | private void dismissDialog() { 123 | runOnMainThread(new Runnable() { 124 | public void run() { 125 | OAuthManagerDialogFragment frag = 126 | (OAuthManagerDialogFragment) fragmentManager.findFragmentByTag(TAG); 127 | 128 | if (frag != null) { 129 | frag.dismissAllowingStateLoss(); 130 | } 131 | } 132 | }); 133 | } 134 | 135 | public void setRequestToken( 136 | final OAuth1RequestToken requestToken 137 | ) { 138 | this.oauth1RequestToken = requestToken; 139 | } 140 | 141 | public void loaded10aAccessToken(final OAuth1AccessToken accessToken) { 142 | Log.d(TAG, "Loaded access token in OAuthManagerFragmentController"); 143 | Log.d(TAG, "AccessToken: " + accessToken + " (raw: " + accessToken.getRawResponse() + ")"); 144 | 145 | mWebView = null; 146 | this.dismissDialog(); 147 | mListener.onOAuth1AccessToken(accessToken); 148 | } 149 | 150 | public void loaded20AccessToken(final OAuth2AccessToken accessToken) { 151 | mWebView = null; 152 | this.dismissDialog(); 153 | mListener.onOAuth2AccessToken(accessToken); 154 | } 155 | 156 | public void onComplete(String url) { 157 | Log.d(TAG, "onComplete called in fragment controller " + url); 158 | // if (mWebView != null) { 159 | // this.getAccessToken(mWebView, url); 160 | // } else { 161 | // this.dismissDialog(); 162 | // } 163 | } 164 | 165 | public void onError(int errorCode, String description, String failingUrl) { 166 | Log.e(TAG, "Error in OAuthManagerFragmentController: " + description); 167 | this.dismissDialog(); 168 | mListener.onRequestTokenError(new Exception(description)); 169 | } 170 | 171 | public void getRequestTokenUrlAndLoad(AdvancedWebView webView) { 172 | mWebView = webView; 173 | LoadRequestTokenTask task = new LoadRequestTokenTask(this, webView); 174 | task.execute(); 175 | } 176 | 177 | public void getAccessToken( 178 | final AdvancedWebView webView, 179 | final String url 180 | ) { 181 | Uri responseUri = Uri.parse(url); 182 | if (authVersion.equals("1.0")) { 183 | String oauthToken = responseUri.getQueryParameter("oauth_token"); 184 | String oauthVerifier = responseUri.getQueryParameter("oauth_verifier"); 185 | Load1AccessTokenTask task = new Load1AccessTokenTask( 186 | this, webView, oauth1RequestToken, oauthVerifier); 187 | task.execute(); 188 | } else if (authVersion.equals("2.0")) { 189 | String code = responseUri.getQueryParameter("code"); 190 | Log.d(TAG, "Called getAccessToken with code: " + code + " at " + url); 191 | if (code != null) { 192 | Load2AccessTokenTask task = new Load2AccessTokenTask( 193 | this, webView, code); 194 | task.execute(); 195 | } else { 196 | this.dismissDialog(); 197 | mListener.onRequestTokenError(new Exception("No token found")); 198 | } 199 | } 200 | } 201 | 202 | ////// TASKS 203 | 204 | private abstract class OAuthTokenTask 205 | extends AsyncTask { 206 | protected AdvancedWebView mWebView; 207 | protected OAuthManagerFragmentController mCtrl; 208 | 209 | public OAuthTokenTask( 210 | OAuthManagerFragmentController ctrl, 211 | AdvancedWebView webView 212 | ) { 213 | this.mCtrl = ctrl; 214 | this.mWebView = webView; 215 | } 216 | 217 | @Override 218 | protected Result doInBackground(Void... params) { 219 | return null; 220 | } 221 | 222 | @Override 223 | protected void onPostExecute(final Result result) {} 224 | } 225 | 226 | private class LoadRequestTokenTask extends OAuthTokenTask { 227 | private OAuth1RequestToken oauth1RequestToken; 228 | 229 | public LoadRequestTokenTask( 230 | OAuthManagerFragmentController ctrl, 231 | AdvancedWebView view 232 | ) { 233 | super(ctrl, view); 234 | } 235 | 236 | @Override 237 | protected String doInBackground(Void... params) { 238 | try { 239 | if (authVersion.equals("1.0")) { 240 | oauth1RequestToken = oauth10aService.getRequestToken(); 241 | 242 | final String requestTokenUrl = 243 | oauth10aService.getAuthorizationUrl(oauth1RequestToken); 244 | return requestTokenUrl; 245 | } else if (authVersion.equals("2.0")) { 246 | 247 | String authorizationUrl; 248 | 249 | if (mCfg.containsKey("authorization_url_params")) { 250 | final HashMap additionalParams = new HashMap(); 251 | additionalParams.put("access_type", "offline"); 252 | additionalParams.put("prompt", "consent"); 253 | 254 | Map authUrlMap = (Map) mCfg.get("authorization_url_params"); 255 | if (authUrlMap != null) { 256 | if (authUrlMap.containsKey("access_type")) { 257 | additionalParams.put("access_type", (String) authUrlMap.get("access_type")); 258 | } 259 | if (authUrlMap.containsKey("prompt")) { 260 | additionalParams.put("prompt", (String) authUrlMap.get("prompt")); 261 | } 262 | } 263 | authorizationUrl = oauth20Service.getAuthorizationUrl(additionalParams); 264 | } else { 265 | authorizationUrl = oauth20Service.getAuthorizationUrl(); 266 | } 267 | 268 | return authorizationUrl; 269 | } else { 270 | return null; 271 | } 272 | } catch (OAuthConnectionException ex) { 273 | Log.e(TAG, "OAuth connection exception: " + ex.getMessage()); 274 | ex.printStackTrace(); 275 | return null; 276 | } catch (IOException ex) { 277 | Log.e(TAG, "IOException occurred: "+ ex.getMessage()); 278 | ex.printStackTrace(); 279 | return null; 280 | } 281 | } 282 | 283 | @Override 284 | protected void onPostExecute(final String url) { 285 | runOnMainThread(new Runnable() { 286 | @Override 287 | public void run() { 288 | if (url == null) { 289 | mCtrl.onError(-1, "No url", ""); 290 | return; 291 | } 292 | if (authVersion.equals("1.0")) { 293 | mCtrl.setRequestToken(oauth1RequestToken); 294 | mWebView.loadUrl(url); 295 | } else if (authVersion.equals("2.0")) { 296 | mWebView.loadUrl(url); 297 | } 298 | } 299 | }); 300 | } 301 | } 302 | 303 | private class Load1AccessTokenTask extends OAuthTokenTask { 304 | private String oauthVerifier; 305 | 306 | public Load1AccessTokenTask( 307 | OAuthManagerFragmentController ctrl, 308 | AdvancedWebView view, 309 | OAuth1RequestToken requestToken, 310 | String oauthVerifier 311 | ) { 312 | super(ctrl, view); 313 | this.oauthVerifier = oauthVerifier; 314 | } 315 | 316 | @Override 317 | protected OAuth1AccessToken doInBackground(Void... params) { 318 | try { 319 | final OAuth1AccessToken accessToken = 320 | (OAuth1AccessToken) oauth10aService.getAccessToken(oauth1RequestToken, oauthVerifier); 321 | return accessToken; 322 | } catch (OAuthConnectionException ex) { 323 | Log.e(TAG, "OAuth connection exception: " + ex.getMessage()); 324 | ex.printStackTrace(); 325 | return null; 326 | } catch (IOException ex) { 327 | Log.e(TAG, "An exception occurred getRequestToken: " + ex.getMessage()); 328 | ex.printStackTrace(); 329 | return null; 330 | } 331 | } 332 | 333 | @Override 334 | protected void onPostExecute(final OAuth1AccessToken accessToken) { 335 | runOnMainThread(new Runnable() { 336 | @Override 337 | public void run() { 338 | if (accessToken == null) { 339 | mCtrl.onError(-1, "No accessToken found", ""); 340 | return; 341 | } 342 | mCtrl.loaded10aAccessToken(accessToken); 343 | } 344 | }); 345 | } 346 | } 347 | 348 | private class Load2AccessTokenTask extends OAuthTokenTask { 349 | private String authorizationCode; 350 | 351 | public Load2AccessTokenTask( 352 | OAuthManagerFragmentController ctrl, 353 | AdvancedWebView view, 354 | String authorizationCode 355 | ) { 356 | super(ctrl, view); 357 | this.authorizationCode = authorizationCode; 358 | } 359 | 360 | @Override 361 | protected OAuth2AccessToken doInBackground(Void... params) { 362 | try { 363 | final OAuth2AccessToken accessToken = 364 | (OAuth2AccessToken) oauth20Service.getAccessToken(authorizationCode); 365 | return accessToken; 366 | } catch (OAuthConnectionException ex) { 367 | Log.e(TAG, "OAuth connection exception: " + ex.getMessage()); 368 | ex.printStackTrace(); 369 | return null; 370 | } catch (IOException ex) { 371 | Log.e(TAG, "An exception occurred getRequestToken: " + ex.getMessage()); 372 | ex.printStackTrace(); 373 | return null; 374 | } 375 | } 376 | 377 | @Override 378 | protected void onPostExecute(final OAuth2AccessToken accessToken) { 379 | runOnMainThread(new Runnable() { 380 | @Override 381 | public void run() { 382 | if (accessToken == null) { 383 | mCtrl.onError(-1, "No accessToken found", ""); 384 | return; 385 | } 386 | mCtrl.loaded20AccessToken(accessToken); 387 | } 388 | }); 389 | } 390 | } 391 | 392 | public String getCallbackUrl() { 393 | return this.callbackUrl; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerModule.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import android.app.Activity; 4 | import android.app.FragmentManager; 5 | import android.content.Context; 6 | import android.support.annotation.Nullable; 7 | import android.util.Log; 8 | 9 | import com.google.gson.Gson; 10 | import com.google.gson.JsonSyntaxException; 11 | 12 | import com.facebook.react.bridge.Arguments; 13 | import com.facebook.react.bridge.Callback; 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | import com.facebook.react.bridge.ReactContext; 16 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 17 | import com.facebook.react.bridge.ReactMethod; 18 | import com.facebook.react.bridge.ReadableArray; 19 | import com.facebook.react.bridge.ReadableMap; 20 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 21 | import com.facebook.react.bridge.ReadableType; 22 | import com.facebook.react.bridge.WritableMap; 23 | import com.github.scribejava.core.model.OAuth1AccessToken; 24 | import com.github.scribejava.core.model.OAuth2AccessToken; 25 | import com.github.scribejava.core.model.OAuthRequest; 26 | import com.github.scribejava.core.model.Response; 27 | import com.github.scribejava.core.model.Verb; 28 | import com.github.scribejava.core.oauth.OAuth10aService; 29 | import com.github.scribejava.core.oauth.OAuth20Service; 30 | 31 | import java.io.IOException; 32 | import java.net.MalformedURLException; 33 | import java.net.URL; 34 | import java.util.ArrayList; 35 | import java.util.HashMap; 36 | import java.util.List; 37 | import java.util.Map; 38 | 39 | class ProviderNotConfiguredException extends Exception { 40 | public ProviderNotConfiguredException(String message) { 41 | super(message); 42 | } 43 | } 44 | 45 | @SuppressWarnings("WeakerAccess") 46 | class OAuthManagerModule extends ReactContextBaseJavaModule { 47 | private static final String TAG = "OAuthManager"; 48 | 49 | private Context context; 50 | private ReactContext mReactContext; 51 | 52 | private HashMap _configuration = new HashMap>(); 53 | private ArrayList _callbackUrls = new ArrayList(); 54 | private OAuthManagerStore _credentialsStore; 55 | 56 | public OAuthManagerModule(ReactApplicationContext reactContext) { 57 | super(reactContext); 58 | mReactContext = reactContext; 59 | _credentialsStore = OAuthManagerStore.getOAuthManagerStore(mReactContext, TAG, Context.MODE_PRIVATE); 60 | Log.d(TAG, "New instance"); 61 | } 62 | 63 | @Override 64 | public String getName() { 65 | return TAG; 66 | } 67 | 68 | @ReactMethod 69 | public void configureProvider( 70 | final String providerName, 71 | final ReadableMap params, 72 | @Nullable final Callback onComplete 73 | ) { 74 | Log.i(TAG, "configureProvider for " + providerName); 75 | 76 | // Save callback url for later 77 | String callbackUrlStr = params.getString("callback_url"); 78 | _callbackUrls.add(callbackUrlStr); 79 | 80 | Log.d(TAG, "Added callback url " + callbackUrlStr + " for providler " + providerName); 81 | 82 | // Keep configuration map 83 | HashMap cfg = new HashMap(); 84 | 85 | ReadableMapKeySetIterator iterator = params.keySetIterator(); 86 | while (iterator.hasNextKey()) { 87 | String key = iterator.nextKey(); 88 | ReadableType readableType = params.getType(key); 89 | switch(readableType) { 90 | case String: 91 | String val = params.getString(key); 92 | // String escapedVal = Uri.encode(val); 93 | cfg.put(key, val); 94 | break; 95 | default: 96 | throw new IllegalArgumentException("Could not read object with key: " + key); 97 | } 98 | } 99 | 100 | _configuration.put(providerName, cfg); 101 | 102 | onComplete.invoke(null, true); 103 | } 104 | 105 | @ReactMethod 106 | public void authorize( 107 | final String providerName, 108 | @Nullable final ReadableMap params, 109 | final Callback callback) 110 | { 111 | try { 112 | final OAuthManagerModule self = this; 113 | final HashMap cfg = this.getConfiguration(providerName); 114 | final String authVersion = (String) cfg.get("auth_version"); 115 | Activity activity = this.getCurrentActivity(); 116 | FragmentManager fragmentManager = activity.getFragmentManager(); 117 | String callbackUrl = "http://localhost/" + providerName; 118 | 119 | OAuthManagerOnAccessTokenListener listener = new OAuthManagerOnAccessTokenListener() { 120 | public void onRequestTokenError(final Exception ex) { 121 | Log.e(TAG, "Exception with request token: " + ex.getMessage()); 122 | _credentialsStore.delete(providerName); 123 | _credentialsStore.commit(); 124 | } 125 | public void onOAuth1AccessToken(final OAuth1AccessToken accessToken) { 126 | _credentialsStore.store(providerName, accessToken); 127 | _credentialsStore.commit(); 128 | 129 | WritableMap resp = self.accessTokenResponse(providerName, cfg, accessToken, authVersion); 130 | callback.invoke(null, resp); 131 | } 132 | public void onOAuth2AccessToken(final OAuth2AccessToken accessToken) { 133 | _credentialsStore.store(providerName, accessToken); 134 | _credentialsStore.commit(); 135 | 136 | WritableMap resp = self.accessTokenResponse(providerName, cfg, accessToken, authVersion); 137 | callback.invoke(null, resp); 138 | } 139 | }; 140 | 141 | if (authVersion.equals("1.0")) { 142 | final OAuth10aService service = 143 | OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, params, callbackUrl); 144 | 145 | OAuthManagerFragmentController ctrl = 146 | new OAuthManagerFragmentController(mReactContext, fragmentManager, providerName, service, callbackUrl); 147 | 148 | ctrl.requestAuth(cfg, listener); 149 | } else if (authVersion.equals("2.0")) { 150 | final OAuth20Service service = 151 | OAuthManagerProviders.getApiFor20Provider(providerName, cfg, params, callbackUrl); 152 | 153 | OAuthManagerFragmentController ctrl = 154 | new OAuthManagerFragmentController(mReactContext, fragmentManager, providerName, service, callbackUrl); 155 | 156 | ctrl.requestAuth(cfg, listener); 157 | } else { 158 | Log.d(TAG, "Auth version unknown: " + (String) cfg.get("auth_version")); 159 | } 160 | } catch (Exception ex) { 161 | Log.d(TAG, "Exception in callback " + ex.getMessage()); 162 | exceptionCallback(ex, callback); 163 | } 164 | } 165 | 166 | @ReactMethod 167 | public void makeRequest( 168 | final String providerName, 169 | final String urlString, 170 | final ReadableMap params, 171 | final Callback onComplete) { 172 | 173 | Log.i(TAG, "makeRequest called for " + providerName + " to " + urlString); 174 | try { 175 | HashMap cfg = this.getConfiguration(providerName); 176 | final String authVersion = (String) cfg.get("auth_version"); 177 | 178 | URL url; 179 | try { 180 | if (urlString.contains("http")) { 181 | url = new URL(urlString); 182 | } else { 183 | String apiHost = (String) cfg.get("api_url"); 184 | url = new URL(apiHost + urlString); 185 | } 186 | } catch (MalformedURLException ex) { 187 | Log.e(TAG, "Bad url. Check request and try again: " + ex.getMessage()); 188 | exceptionCallback(ex, onComplete); 189 | return; 190 | } 191 | 192 | String httpMethod; 193 | if (params.hasKey("method")) { 194 | httpMethod = params.getString("method"); 195 | } else { 196 | httpMethod = "GET"; 197 | } 198 | 199 | Verb httpVerb; 200 | if (httpMethod.equalsIgnoreCase("GET")) { 201 | httpVerb = Verb.GET; 202 | } else if (httpMethod.equalsIgnoreCase("POST")) { 203 | httpVerb = Verb.POST; 204 | } else if (httpMethod.equalsIgnoreCase("PUT")) { 205 | httpVerb = Verb.PUT; 206 | } else if (httpMethod.equalsIgnoreCase("DELETE")) { 207 | httpVerb = Verb.DELETE; 208 | } else if (httpMethod.equalsIgnoreCase("OPTIONS")) { 209 | httpVerb = Verb.OPTIONS; 210 | } else if (httpMethod.equalsIgnoreCase("HEAD")) { 211 | httpVerb = Verb.HEAD; 212 | } else if (httpMethod.equalsIgnoreCase("PATCH")) { 213 | httpVerb = Verb.PATCH; 214 | } else if (httpMethod.equalsIgnoreCase("TRACE")) { 215 | httpVerb = Verb.TRACE; 216 | } else { 217 | httpVerb = Verb.GET; 218 | } 219 | 220 | ReadableMap requestParams = null; 221 | if (params != null && params.hasKey("params")) { 222 | requestParams = params.getMap("params"); 223 | } 224 | OAuthRequest request = oauthRequestWithParams(providerName, cfg, authVersion, httpVerb, url, requestParams); 225 | 226 | if (authVersion.equals("1.0")) { 227 | final OAuth10aService service = 228 | OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, requestParams, null); 229 | OAuth1AccessToken token = _credentialsStore.get(providerName, OAuth1AccessToken.class); 230 | 231 | service.signRequest(token, request); 232 | } else if (authVersion.equals("2.0")) { 233 | final OAuth20Service service = 234 | OAuthManagerProviders.getApiFor20Provider(providerName, cfg, requestParams, null); 235 | OAuth2AccessToken token = _credentialsStore.get(providerName, OAuth2AccessToken.class); 236 | 237 | service.signRequest(token, request); 238 | } else { 239 | // Some kind of error here 240 | Log.e(TAG, "An error occurred"); 241 | WritableMap err = Arguments.createMap(); 242 | err.putString("status", "error"); 243 | err.putString("msg", "A weird error occurred"); 244 | onComplete.invoke(err); 245 | return; 246 | } 247 | 248 | final Response response = request.send(); 249 | final String rawBody = response.getBody(); 250 | 251 | Log.d(TAG, "rawBody: " + rawBody); 252 | // final Object response = new Gson().fromJson(rawBody, Object.class); 253 | 254 | WritableMap resp = Arguments.createMap(); 255 | resp.putInt("status", response.getCode()); 256 | resp.putString("data", rawBody); 257 | onComplete.invoke(null, resp); 258 | 259 | } catch (IOException ex) { 260 | Log.e(TAG, "IOException when making request: " + ex.getMessage()); 261 | ex.printStackTrace(); 262 | exceptionCallback(ex, onComplete); 263 | } catch (Exception ex) { 264 | Log.e(TAG, "Exception when making request: " + ex.getMessage()); 265 | exceptionCallback(ex, onComplete); 266 | } 267 | } 268 | 269 | private OAuthRequest oauthRequestWithParams( 270 | final String providerName, 271 | final HashMap cfg, 272 | final String authVersion, 273 | final Verb httpVerb, 274 | final URL url, 275 | @Nullable final ReadableMap params 276 | ) throws Exception { 277 | OAuthRequest request; 278 | // OAuthConfig config; 279 | 280 | if (authVersion.equals("1.0")) { 281 | // final OAuth10aService service = 282 | // OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, null, null); 283 | OAuth1AccessToken oa1token = _credentialsStore.get(providerName, OAuth1AccessToken.class); 284 | request = OAuthManagerProviders.getRequestForProvider( 285 | providerName, 286 | httpVerb, 287 | oa1token, 288 | url, 289 | cfg, 290 | params); 291 | 292 | // config = service.getConfig(); 293 | // request = new OAuthRequest(httpVerb, url.toString(), config); 294 | } else if (authVersion.equals("2.0")) { 295 | // final OAuth20Service service = 296 | // OAuthManagerProviders.getApiFor20Provider(providerName, cfg, null, null); 297 | // oa2token = _credentialsStore.get(providerName, OAuth2AccessToken.class); 298 | 299 | OAuth2AccessToken oa2token = _credentialsStore.get(providerName, OAuth2AccessToken.class); 300 | request = OAuthManagerProviders.getRequestForProvider( 301 | providerName, 302 | httpVerb, 303 | oa2token, 304 | url, 305 | cfg, 306 | params); 307 | 308 | // config = service.getConfig(); 309 | // request = new OAuthRequest(httpVerb, url.toString(), config); 310 | } else { 311 | Log.e(TAG, "Error in making request method"); 312 | throw new Exception("Provider not handled yet"); 313 | } 314 | 315 | return request; 316 | } 317 | 318 | @ReactMethod 319 | public void getSavedAccounts(final ReadableMap options, final Callback onComplete) { 320 | // Log.d(TAG, "getSavedAccounts"); 321 | } 322 | 323 | @ReactMethod 324 | public void getSavedAccount( 325 | final String providerName, 326 | final ReadableMap options, 327 | final Callback onComplete) 328 | { 329 | try { 330 | HashMap cfg = this.getConfiguration(providerName); 331 | final String authVersion = (String) cfg.get("auth_version"); 332 | 333 | Log.i(TAG, "getSavedAccount for " + providerName); 334 | 335 | if (authVersion.equals("1.0")) { 336 | OAuth1AccessToken token = _credentialsStore.get(providerName, OAuth1AccessToken.class); 337 | Log.d(TAG, "Found token: " + token); 338 | if (token == null || token.equals("")) { 339 | throw new Exception("No token found"); 340 | } 341 | 342 | WritableMap resp = this.accessTokenResponse(providerName, cfg, token, authVersion); 343 | onComplete.invoke(null, resp); 344 | } else if (authVersion.equals("2.0")) { 345 | OAuth2AccessToken token = _credentialsStore.get(providerName, OAuth2AccessToken.class); 346 | 347 | if (token == null || token.equals("")) { 348 | throw new Exception("No token found"); 349 | } 350 | WritableMap resp = this.accessTokenResponse(providerName, cfg, token, authVersion); 351 | onComplete.invoke(null, resp); 352 | } else { 353 | 354 | } 355 | } catch (ProviderNotConfiguredException ex) { 356 | Log.e(TAG, "Provider not yet configured: " + providerName); 357 | exceptionCallback(ex, onComplete); 358 | } catch (Exception ex) { 359 | Log.e(TAG, "An exception occurred getSavedAccount: " + ex.getMessage()); 360 | ex.printStackTrace(); 361 | exceptionCallback(ex, onComplete); 362 | } 363 | 364 | } 365 | 366 | @ReactMethod 367 | public void deauthorize(final String providerName, final Callback onComplete) { 368 | try { 369 | Log.i(TAG, "deauthorizing " + providerName); 370 | HashMap cfg = this.getConfiguration(providerName); 371 | final String authVersion = (String) cfg.get("auth_version"); 372 | 373 | _credentialsStore.delete(providerName); 374 | 375 | WritableMap resp = Arguments.createMap(); 376 | resp.putString("status", "ok"); 377 | 378 | onComplete.invoke(null, resp); 379 | } catch (Exception ex) { 380 | exceptionCallback(ex, onComplete); 381 | } 382 | } 383 | 384 | 385 | private HashMap getConfiguration( 386 | final String providerName 387 | ) throws Exception { 388 | if (!_configuration.containsKey(providerName)) { 389 | throw new ProviderNotConfiguredException("Provider not configured: " + providerName); 390 | } 391 | 392 | HashMap cfg = (HashMap) _configuration.get(providerName); 393 | return cfg; 394 | } 395 | 396 | private WritableMap accessTokenResponse( 397 | final String providerName, 398 | final HashMap cfg, 399 | final OAuth1AccessToken accessToken, 400 | final String oauthVersion 401 | ) { 402 | WritableMap resp = Arguments.createMap(); 403 | WritableMap response = Arguments.createMap(); 404 | 405 | Log.d(TAG, "Credential raw response: " + accessToken.getRawResponse()); 406 | 407 | /* Some things return as JSON, some as x-www-form-urlencoded (querystring) */ 408 | 409 | Map accessTokenMap = null; 410 | try { 411 | accessTokenMap = new Gson().fromJson(accessToken.getRawResponse(), Map.class); 412 | } catch (JsonSyntaxException e) { 413 | /* 414 | failed to parse as JSON, so turn it into a HashMap which looks like the one we'd 415 | get back from the JSON parser, so the rest of the code continues unchanged. 416 | */ 417 | Log.d(TAG, "Credential looks like a querystring; parsing as such"); 418 | accessTokenMap = new HashMap(); 419 | accessTokenMap.put("user_id", accessToken.getParameter("user_id")); 420 | accessTokenMap.put("oauth_token_secret", accessToken.getParameter("oauth_token_secret")); 421 | accessTokenMap.put("token_type", accessToken.getParameter("token_type")); 422 | } 423 | 424 | 425 | resp.putString("status", "ok"); 426 | resp.putBoolean("authorized", true); 427 | resp.putString("provider", providerName); 428 | 429 | String uuid = accessToken.getParameter("user_id"); 430 | response.putString("uuid", uuid); 431 | String oauthTokenSecret = (String) accessToken.getParameter("oauth_token_secret"); 432 | 433 | String tokenType = (String) accessToken.getParameter("token_type"); 434 | if (tokenType == null) { 435 | tokenType = "Bearer"; 436 | } 437 | 438 | String consumerKey = (String) cfg.get("consumer_key"); 439 | 440 | WritableMap credentials = Arguments.createMap(); 441 | credentials.putString("access_token", accessToken.getToken()); 442 | credentials.putString("access_token_secret", oauthTokenSecret); 443 | credentials.putString("type", tokenType); 444 | credentials.putString("consumerKey", consumerKey); 445 | 446 | response.putMap("credentials", credentials); 447 | 448 | resp.putMap("response", response); 449 | 450 | return resp; 451 | } 452 | 453 | private WritableMap accessTokenResponse( 454 | final String providerName, 455 | final HashMap cfg, 456 | final OAuth2AccessToken accessToken, 457 | final String oauthVersion 458 | ) { 459 | WritableMap resp = Arguments.createMap(); 460 | WritableMap response = Arguments.createMap(); 461 | 462 | resp.putString("status", "ok"); 463 | resp.putBoolean("authorized", true); 464 | resp.putString("provider", providerName); 465 | 466 | String uuid = accessToken.getParameter("user_id"); 467 | response.putString("uuid", uuid); 468 | 469 | WritableMap credentials = Arguments.createMap(); 470 | Log.d(TAG, "Credential raw response: " + accessToken.getRawResponse()); 471 | 472 | credentials.putString("accessToken", accessToken.getAccessToken()); 473 | String authHeader; 474 | 475 | String tokenType = accessToken.getTokenType(); 476 | if (tokenType == null) { 477 | tokenType = "Bearer"; 478 | } 479 | 480 | String scope = accessToken.getScope(); 481 | if (scope == null) { 482 | scope = (String) cfg.get("scopes"); 483 | } 484 | 485 | String clientID = (String) cfg.get("client_id"); 486 | String idToken = accessToken.getParameter("id_token"); 487 | 488 | authHeader = tokenType + " " + accessToken.getAccessToken(); 489 | credentials.putString("authorizationHeader", authHeader); 490 | credentials.putString("type", tokenType); 491 | credentials.putString("scopes", scope); 492 | credentials.putString("clientID", clientID); 493 | credentials.putString("idToken", idToken); 494 | response.putMap("credentials", credentials); 495 | 496 | resp.putMap("response", response); 497 | 498 | return resp; 499 | } 500 | 501 | 502 | private void exceptionCallback(Exception ex, final Callback onFail) { 503 | WritableMap error = Arguments.createMap(); 504 | error.putInt("errorCode", ex.hashCode()); 505 | error.putString("errorMessage", ex.getMessage()); 506 | error.putString("allErrorMessage", ex.toString()); 507 | 508 | onFail.invoke(error); 509 | } 510 | 511 | public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { 512 | Map deconstructedMap = new HashMap<>(); 513 | if (readableMap == null) { 514 | return deconstructedMap; 515 | } 516 | 517 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 518 | while (iterator.hasNextKey()) { 519 | String key = iterator.nextKey(); 520 | ReadableType type = readableMap.getType(key); 521 | switch (type) { 522 | case Null: 523 | deconstructedMap.put(key, null); 524 | break; 525 | case Boolean: 526 | deconstructedMap.put(key, readableMap.getBoolean(key)); 527 | break; 528 | case Number: 529 | deconstructedMap.put(key, readableMap.getDouble(key)); 530 | break; 531 | case String: 532 | deconstructedMap.put(key, readableMap.getString(key)); 533 | break; 534 | case Map: 535 | deconstructedMap.put(key, OAuthManagerModule.recursivelyDeconstructReadableMap(readableMap.getMap(key))); 536 | break; 537 | case Array: 538 | deconstructedMap.put(key, OAuthManagerModule.recursivelyDeconstructReadableArray(readableMap.getArray(key))); 539 | break; 540 | default: 541 | throw new IllegalArgumentException("Could not convert object with key: " + key + "."); 542 | } 543 | 544 | } 545 | return deconstructedMap; 546 | } 547 | 548 | public static List recursivelyDeconstructReadableArray(ReadableArray readableArray) { 549 | List deconstructedList = new ArrayList<>(readableArray.size()); 550 | for (int i = 0; i < readableArray.size(); i++) { 551 | ReadableType indexType = readableArray.getType(i); 552 | switch (indexType) { 553 | case Null: 554 | deconstructedList.add(i, null); 555 | break; 556 | case Boolean: 557 | deconstructedList.add(i, readableArray.getBoolean(i)); 558 | break; 559 | case Number: 560 | deconstructedList.add(i, readableArray.getDouble(i)); 561 | break; 562 | case String: 563 | deconstructedList.add(i, readableArray.getString(i)); 564 | break; 565 | case Map: 566 | deconstructedList.add(i, OAuthManagerModule.recursivelyDeconstructReadableMap(readableArray.getMap(i))); 567 | break; 568 | case Array: 569 | deconstructedList.add(i, OAuthManagerModule.recursivelyDeconstructReadableArray(readableArray.getArray(i))); 570 | break; 571 | default: 572 | throw new IllegalArgumentException("Could not convert object at index " + i + "."); 573 | } 574 | } 575 | return deconstructedList; 576 | } 577 | } 578 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerOnAccessTokenListener.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import java.io.IOException; 4 | import com.github.scribejava.core.model.OAuth1AccessToken; 5 | import com.github.scribejava.core.model.OAuth2AccessToken; 6 | 7 | public interface OAuthManagerOnAccessTokenListener { 8 | void onOAuth1AccessToken(final OAuth1AccessToken accessToken); 9 | void onOAuth2AccessToken(final OAuth2AccessToken accessToken); 10 | void onRequestTokenError(final Exception ex); 11 | } -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerPackage.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.JavaScriptModule; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | 11 | import java.util.List; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | 15 | @SuppressWarnings("unused") 16 | public class OAuthManagerPackage implements ReactPackage { 17 | private Context mContext; 18 | 19 | public OAuthManagerPackage() { 20 | } 21 | /** 22 | * @param reactContext react application context that can be used to create modules 23 | * @return list of native modules to register with the newly created catalyst instance 24 | */ 25 | public List createNativeModules(ReactApplicationContext reactContext) { 26 | List modules = new ArrayList<>(); 27 | modules.add(new OAuthManagerModule(reactContext)); 28 | return modules; 29 | } 30 | 31 | /** 32 | * @return list of JS modules to register with the newly created catalyst instance. 33 | *

34 | * IMPORTANT: Note that only modules that needs to be accessible from the native code should be 35 | * listed here. Also listing a native module here doesn't imply that the JS implementation of it 36 | * will be automatically included in the JS bundle. 37 | */ 38 | 39 | public List> createJSModules() { 40 | return Collections.emptyList(); 41 | } 42 | 43 | /** 44 | * @param reactContext 45 | * @return a list of view managers that should be registered with {@link UIManagerModule} 46 | */ 47 | public List createViewManagers(ReactApplicationContext reactContext) { 48 | return Collections.emptyList(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerProviders.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import android.util.Log; 4 | import java.util.HashMap; 5 | import java.util.Random; 6 | import java.util.List; 7 | import android.support.annotation.Nullable; 8 | import java.net.URL; 9 | import java.net.MalformedURLException; 10 | import android.text.TextUtils; 11 | import java.util.Arrays; 12 | 13 | import com.github.scribejava.core.model.Verb; 14 | import com.github.scribejava.core.builder.api.BaseApi; 15 | import com.github.scribejava.core.oauth.OAuthService; 16 | import com.github.scribejava.core.oauth.OAuth10aService; 17 | import com.github.scribejava.core.builder.ServiceBuilder; 18 | import com.github.scribejava.core.model.OAuth1AccessToken; 19 | import com.github.scribejava.core.model.OAuth1RequestToken; 20 | import com.github.scribejava.core.model.OAuthRequest; 21 | import com.github.scribejava.core.model.OAuthConfig; 22 | 23 | import com.github.scribejava.core.model.OAuth2AccessToken; 24 | import com.github.scribejava.core.oauth.OAuth20Service; 25 | 26 | import com.github.scribejava.apis.TwitterApi; 27 | import com.github.scribejava.apis.FacebookApi; 28 | import com.github.scribejava.apis.GoogleApi20; 29 | import com.github.scribejava.apis.GitHubApi; 30 | 31 | import com.github.scribejava.apis.ConfigurableApi; 32 | import com.github.scribejava.apis.SlackApi; 33 | 34 | import com.facebook.react.bridge.ReadableMap; 35 | import com.facebook.react.bridge.ReadableArray; 36 | import com.facebook.react.bridge.ReadableType; 37 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 38 | 39 | public class OAuthManagerProviders { 40 | private static final String TAG = "OAuthManagerProviders"; 41 | 42 | static public OAuth10aService getApiFor10aProvider( 43 | final String providerName, 44 | final HashMap params, 45 | @Nullable final ReadableMap opts, 46 | final String callbackUrl 47 | ) { 48 | if (providerName.equalsIgnoreCase("twitter")) { 49 | return OAuthManagerProviders.twitterService(params, opts, callbackUrl); 50 | } else { 51 | return null; 52 | } 53 | } 54 | 55 | static public OAuth20Service getApiFor20Provider( 56 | final String providerName, 57 | final HashMap params, 58 | @Nullable final ReadableMap opts, 59 | final String callbackUrl 60 | ) { 61 | if (providerName.equalsIgnoreCase("facebook")) { 62 | return OAuthManagerProviders.facebookService(params, opts, callbackUrl); 63 | } 64 | 65 | if (providerName.equalsIgnoreCase("google")) { 66 | return OAuthManagerProviders.googleService(params, opts, callbackUrl); 67 | } 68 | 69 | if (providerName.equalsIgnoreCase("github")) { 70 | return OAuthManagerProviders.githubService(params, opts, callbackUrl); 71 | } 72 | 73 | if (providerName.equalsIgnoreCase("slack")) { 74 | return OAuthManagerProviders.slackService(params, opts, callbackUrl); 75 | } 76 | 77 | if (params.containsKey("access_token_url") && params.containsKey("authorize_url")) { 78 | return OAuthManagerProviders.configurableService(params, opts, callbackUrl); 79 | } 80 | 81 | return null; 82 | } 83 | 84 | static public OAuthRequest getRequestForProvider( 85 | final String providerName, 86 | final Verb httpVerb, 87 | final OAuth1AccessToken oa1token, 88 | final URL url, 89 | final HashMap cfg, 90 | @Nullable final ReadableMap params 91 | ) { 92 | final OAuth10aService service = 93 | OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, null, null); 94 | 95 | String token = oa1token.getToken(); 96 | OAuthConfig config = service.getConfig(); 97 | OAuthRequest request = new OAuthRequest(httpVerb, url.toString(), config); 98 | 99 | request = OAuthManagerProviders.addParametersToRequest(request, token, params); 100 | // Nothing special for Twitter 101 | return request; 102 | } 103 | 104 | static public OAuthRequest getRequestForProvider( 105 | final String providerName, 106 | final Verb httpVerb, 107 | final OAuth2AccessToken oa2token, 108 | final URL url, 109 | final HashMap cfg, 110 | @Nullable final ReadableMap params 111 | ) { 112 | final OAuth20Service service = 113 | OAuthManagerProviders.getApiFor20Provider(providerName, cfg, null, null); 114 | 115 | OAuthConfig config = service.getConfig(); 116 | OAuthRequest request = new OAuthRequest(httpVerb, url.toString(), config); 117 | String token = oa2token.getAccessToken(); 118 | 119 | request = OAuthManagerProviders.addParametersToRequest(request, token, params); 120 | 121 | // 122 | Log.d(TAG, "Making request for " + providerName + " to add token " + token); 123 | // Need a way to standardize this, but for now 124 | if (providerName.equalsIgnoreCase("slack")) { 125 | request.addParameter("token", token); 126 | } 127 | 128 | return request; 129 | } 130 | 131 | // Helper to add parameters to the request 132 | static private OAuthRequest addParametersToRequest( 133 | OAuthRequest request, 134 | final String access_token, 135 | @Nullable final ReadableMap params 136 | ) { 137 | if (params != null && params.hasKey("params")) { 138 | ReadableMapKeySetIterator iterator = params.keySetIterator(); 139 | while (iterator.hasNextKey()) { 140 | String key = iterator.nextKey(); 141 | ReadableType readableType = params.getType(key); 142 | switch(readableType) { 143 | case String: 144 | String val = params.getString(key); 145 | // String escapedVal = Uri.encode(val); 146 | if (val.equals("access_token")) { 147 | val = access_token; 148 | } 149 | request.addParameter(key, val); 150 | break; 151 | default: 152 | throw new IllegalArgumentException("Could not read object with key: " + key); 153 | } 154 | } 155 | } 156 | return request; 157 | } 158 | 159 | private static OAuth10aService twitterService( 160 | final HashMap cfg, 161 | @Nullable final ReadableMap opts, 162 | final String callbackUrl) { 163 | String consumerKey = (String) cfg.get("consumer_key"); 164 | String consumerSecret = (String) cfg.get("consumer_secret"); 165 | 166 | ServiceBuilder builder = new ServiceBuilder() 167 | .apiKey(consumerKey) 168 | .apiSecret(consumerSecret) 169 | .debug(); 170 | 171 | String scopes = (String) cfg.get("scopes"); 172 | if (scopes != null) { 173 | // String scopeStr = OAuthManagerProviders.getScopeString(scopes, "+"); 174 | // Log.d(TAG, "scopeStr: " + scopeStr); 175 | // builder.scope(scopeStr); 176 | } 177 | 178 | if (callbackUrl != null) { 179 | builder.callback(callbackUrl); 180 | } 181 | 182 | return builder.build(TwitterApi.instance()); 183 | } 184 | 185 | private static OAuth20Service facebookService( 186 | final HashMap cfg, 187 | @Nullable final ReadableMap opts, 188 | final String callbackUrl) { 189 | ServiceBuilder builder = OAuthManagerProviders._oauth2ServiceBuilder(cfg, opts, callbackUrl); 190 | return builder.build(FacebookApi.instance()); 191 | } 192 | 193 | private static OAuth20Service googleService( 194 | final HashMap cfg, 195 | @Nullable final ReadableMap opts, 196 | final String callbackUrl) 197 | { 198 | ServiceBuilder builder = OAuthManagerProviders._oauth2ServiceBuilder(cfg, opts, callbackUrl); 199 | return builder.build(GoogleApi20.instance()); 200 | } 201 | 202 | private static OAuth20Service githubService( 203 | final HashMap cfg, 204 | @Nullable final ReadableMap opts, 205 | final String callbackUrl) 206 | { 207 | 208 | ServiceBuilder builder = OAuthManagerProviders._oauth2ServiceBuilder(cfg, opts, callbackUrl); 209 | return builder.build(GitHubApi.instance()); 210 | } 211 | 212 | private static OAuth20Service configurableService( 213 | final HashMap cfg, 214 | @Nullable final ReadableMap opts, 215 | final String callbackUrl 216 | ) { 217 | ServiceBuilder builder = OAuthManagerProviders._oauth2ServiceBuilder(cfg, opts, callbackUrl); 218 | Log.d(TAG, "Creating ConfigurableApi"); 219 | //Log.d(TAG, " authorize_url: " + cfg.get("authorize_url")); 220 | //Log.d(TAG, " access_token_url: " + cfg.get("access_token_url")); 221 | ConfigurableApi api = ConfigurableApi.instance() 222 | .setAccessTokenEndpoint((String) cfg.get("access_token_url")) 223 | .setAuthorizationBaseUrl((String) cfg.get("authorize_url")); 224 | if (cfg.containsKey("access_token_verb")) { 225 | //Log.d(TAG, " access_token_verb: " + cfg.get("access_token_verb")); 226 | api.setAccessTokenVerb((String) cfg.get("access_token_verb")); 227 | } 228 | 229 | return builder.build(api); 230 | } 231 | 232 | private static OAuth20Service slackService( 233 | final HashMap cfg, 234 | @Nullable final ReadableMap opts, 235 | final String callbackUrl 236 | ) { 237 | 238 | Log.d(TAG, "Make the builder: " + SlackApi.class); 239 | ServiceBuilder builder = OAuthManagerProviders._oauth2ServiceBuilder(cfg, opts, callbackUrl); 240 | return builder.build(SlackApi.instance()); 241 | } 242 | 243 | private static ServiceBuilder _oauth2ServiceBuilder( 244 | final HashMap cfg, 245 | @Nullable final ReadableMap opts, 246 | final String callbackUrl 247 | ) { 248 | String clientKey = (String) cfg.get("client_id"); 249 | String clientSecret = (String) cfg.get("client_secret"); 250 | String state; 251 | if (cfg.containsKey("state")) { 252 | state = (String) cfg.get("state"); 253 | } else { 254 | state = TAG + new Random().nextInt(999_999); 255 | } 256 | 257 | // Builder 258 | ServiceBuilder builder = new ServiceBuilder() 259 | .apiKey(clientKey) 260 | .apiSecret(clientSecret) 261 | .state(state) 262 | .debug(); 263 | 264 | String scopes = ""; 265 | if (cfg.containsKey("scopes")) { 266 | scopes = (String) cfg.get("scopes"); 267 | String scopeStr = OAuthManagerProviders.getScopeString(scopes, ","); 268 | builder.scope(scopeStr); 269 | } 270 | 271 | if (opts != null && opts.hasKey("scopes")) { 272 | scopes = (String) opts.getString("scopes"); 273 | String scopeStr = OAuthManagerProviders.getScopeString(scopes, ","); 274 | builder.scope(scopeStr); 275 | } 276 | 277 | if (callbackUrl != null) { 278 | builder.callback(callbackUrl); 279 | } 280 | 281 | return builder; 282 | } 283 | 284 | /** 285 | * Convert a list of scopes by space or string into an array 286 | */ 287 | private static String getScopeString( 288 | final String scopes, 289 | final String joinBy 290 | ) { 291 | List array = Arrays.asList(scopes.replaceAll("\\s", "").split("[ ,]+")); 292 | Log.d(TAG, "array: " + array + " (" + array.size() + ") from " + scopes); 293 | return TextUtils.join(joinBy, array); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/OAuthManagerStore.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.oauth; 2 | 3 | import java.lang.reflect.Type; 4 | import android.content.Context; 5 | import android.util.Log; 6 | import java.util.Set; 7 | import java.util.HashMap; 8 | import android.content.Context; 9 | import android.net.Uri; 10 | import android.os.Handler; 11 | import android.content.SharedPreferences; 12 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 13 | import com.google.gson.Gson; 14 | import android.text.TextUtils; 15 | import java.util.Collection; 16 | 17 | import com.github.scribejava.core.model.OAuth1AccessToken; 18 | import com.github.scribejava.core.model.OAuth2AccessToken; 19 | import com.github.scribejava.core.model.Token; 20 | import com.google.gson.reflect.TypeToken; 21 | 22 | public class OAuthManagerStore { 23 | private static final String TAG = "OAuthManagerStore"; 24 | private static final String MAP_TAG = "CredentialList"; 25 | // private static final Type MAP_TYPE = new TypeToken>() {}.getType(); 26 | private static OAuthManagerStore oauthManagerStore; 27 | private Context context; 28 | 29 | private SharedPreferences prefs; 30 | private SharedPreferences.Editor editor; 31 | private OnSharedPreferenceChangeListener listener; 32 | 33 | public OAuthManagerStore(Context ctx) { 34 | this(ctx, TAG, Context.MODE_PRIVATE); 35 | } 36 | 37 | public OAuthManagerStore(Context ctx, String name) { 38 | this(ctx, name, Context.MODE_PRIVATE); 39 | } 40 | 41 | public OAuthManagerStore(Context ctx, String name, int mode) { 42 | // setup credential store 43 | this.context = ctx; 44 | this.prefs = ctx.getSharedPreferences(name, Context.MODE_PRIVATE); 45 | editor = this.prefs.edit(); 46 | listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 47 | @Override 48 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 49 | Log.d(TAG, "Preferences changed: " + key); 50 | } 51 | }; 52 | prefs.registerOnSharedPreferenceChangeListener(listener); 53 | } 54 | 55 | public static OAuthManagerStore getOAuthManagerStore(Context ctx, String name, int mode) { 56 | if (oauthManagerStore == null) { 57 | oauthManagerStore = new OAuthManagerStore(ctx, name, mode); 58 | } 59 | return oauthManagerStore; 60 | } 61 | 62 | public void store(String providerName, final OAuth1AccessToken accessToken) { 63 | if (accessToken == null) { 64 | throw new IllegalArgumentException("Token is null"); 65 | } 66 | if (providerName.equals("") || providerName == null) { 67 | throw new IllegalArgumentException("Provider is null"); 68 | } 69 | editor.putString(providerName, new Gson().toJson(accessToken)); 70 | } 71 | 72 | public void store(String providerName, final OAuth2AccessToken accessToken) { 73 | if (accessToken == null) { 74 | throw new IllegalArgumentException("Token is null"); 75 | } 76 | if (providerName.equals("") || providerName == null) { 77 | throw new IllegalArgumentException("Provider is null"); 78 | } 79 | editor.putString(providerName, new Gson().toJson(accessToken)); 80 | } 81 | 82 | 83 | public void commit() { 84 | editor.commit(); 85 | } 86 | 87 | public T get(String providerName, Class a) { 88 | String gson = this.prefs.getString(providerName, null); 89 | if (gson == null) { 90 | return null; 91 | } else { 92 | try { 93 | return new Gson().fromJson(gson, a); 94 | } catch (Exception ex) { 95 | throw new IllegalArgumentException("Object storaged with key " + providerName + " is instanceof other class"); 96 | } 97 | } 98 | } 99 | 100 | public void delete(String providerName) { 101 | editor.remove(providerName); 102 | this.commit(); 103 | } 104 | } -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/services/ConfigurableApi.java: -------------------------------------------------------------------------------- 1 | package com.github.scribejava.apis; 2 | 3 | import android.util.Log; 4 | 5 | import com.github.scribejava.core.builder.api.DefaultApi20; 6 | import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor; 7 | import com.github.scribejava.core.extractors.TokenExtractor; 8 | import com.github.scribejava.core.model.OAuth2AccessToken; 9 | import com.github.scribejava.core.model.Verb; 10 | 11 | public class ConfigurableApi extends DefaultApi20 { 12 | 13 | private String accessTokenEndpoint; 14 | 15 | private String authorizationBaseUrl; 16 | 17 | private Verb accessTokenVerb = Verb.GET; 18 | 19 | protected ConfigurableApi() { 20 | } 21 | 22 | private static class InstanceHolder { 23 | private static final ConfigurableApi INSTANCE = new ConfigurableApi(); 24 | } 25 | 26 | public static ConfigurableApi instance() { 27 | return InstanceHolder.INSTANCE; 28 | } 29 | 30 | public ConfigurableApi setAccessTokenEndpoint(String endpoint) { 31 | accessTokenEndpoint = endpoint; 32 | return this; 33 | } 34 | 35 | public ConfigurableApi setAuthorizationBaseUrl(String baseUrl) { 36 | authorizationBaseUrl = baseUrl; 37 | return this; 38 | } 39 | 40 | public ConfigurableApi setAccessTokenVerb(String verb) { 41 | if (verb.equalsIgnoreCase("GET")) { 42 | accessTokenVerb = Verb.GET; 43 | } else if (verb.equalsIgnoreCase("POST")) { 44 | accessTokenVerb = Verb.POST; 45 | } else { 46 | Log.e("ConfigurableApi", "Expected GET or POST string values for accessTokenVerb."); 47 | } 48 | 49 | return this; 50 | } 51 | 52 | @Override 53 | public Verb getAccessTokenVerb() { 54 | return accessTokenVerb; 55 | } 56 | 57 | @Override 58 | public String getAccessTokenEndpoint() { 59 | return accessTokenEndpoint; 60 | } 61 | 62 | @Override 63 | protected String getAuthorizationBaseUrl() { 64 | return authorizationBaseUrl; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/oauth/services/SlackApi.java: -------------------------------------------------------------------------------- 1 | package com.github.scribejava.apis; 2 | 3 | import android.util.Log; 4 | 5 | import com.github.scribejava.core.builder.api.DefaultApi20; 6 | import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor; 7 | import com.github.scribejava.core.extractors.TokenExtractor; 8 | import com.github.scribejava.core.model.OAuth2AccessToken; 9 | import com.github.scribejava.core.model.Verb; 10 | 11 | public class SlackApi extends DefaultApi20 { 12 | 13 | protected SlackApi() { 14 | } 15 | 16 | private static class InstanceHolder { 17 | private static final SlackApi INSTANCE = new SlackApi(); 18 | } 19 | 20 | public static SlackApi instance() { 21 | return InstanceHolder.INSTANCE; 22 | } 23 | 24 | @Override 25 | public Verb getAccessTokenVerb() { 26 | return Verb.GET; 27 | } 28 | 29 | @Override 30 | public String getAccessTokenEndpoint() { 31 | return "https://slack.com/api/oauth.access"; 32 | } 33 | 34 | @Override 35 | protected String getAuthorizationBaseUrl() { 36 | return "https://slack.com/oauth/authorize"; 37 | } 38 | } -------------------------------------------------------------------------------- /android/src/main/res/layout/webview_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /bin/cocoapods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/cocoapods.sh 4 | 5 | ios_dir=`pwd`/ios 6 | if [ ! -d $ios_dir ] 7 | then 8 | exit 0 9 | fi 10 | 11 | podfile="$ios_dir/Podfile" 12 | template=`pwd`/node_modules/react-native-oauth/ios/Podfile.template 13 | 14 | echo "Checking Podfile in iOS project ($podfile)" 15 | 16 | if [ -f $podfile ] 17 | then 18 | echo "" 19 | echo "Found an existing Podfile, Do you want to override it? [N/y]" 20 | read generate_env_file 21 | 22 | if [ "$generate_env_file" != "y" ] 23 | then 24 | 25 | echo "Add the following pods": 26 | echo "" 27 | echo "" 28 | cat $template 29 | echo "" 30 | echo "" 31 | echo "and run 'pod install' to install OAuth for iOS" 32 | exit 0 33 | fi 34 | 35 | rm -f $podfile 36 | rm -f "$podfile.lock" 37 | fi 38 | 39 | echo "Adding Podfile to iOS project" 40 | 41 | cd ios 42 | pod init >/dev/null 2>&1 43 | cat $template >> $podfile 44 | cd .. 45 | 46 | echo "Installing Pods" 47 | 48 | pod install --project-directory=ios 49 | -------------------------------------------------------------------------------- /bin/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/prepare.sh 4 | 5 | echo "Preparing to link react-native-firestack for iOS" 6 | 7 | echo "Checking CocoaPods..." 8 | has_cocoapods=`which pod >/dev/null 2>&1` 9 | if [ -z "$has_cocoapods" ] 10 | then 11 | echo "CocoaPods already installed" 12 | else 13 | echo "Installing CocoaPods..." 14 | gem install cocoapods 15 | fi -------------------------------------------------------------------------------- /ios/OAuthManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B76E094E1E768CE100A9AF9A /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = B76E094B1E768CE100A9AF9A /* README.md */; }; 11 | B76E094F1E768CE100A9AF9A /* XMLReader.h in Headers */ = {isa = PBXBuildFile; fileRef = B76E094C1E768CE100A9AF9A /* XMLReader.h */; }; 12 | B76E09501E768CE100A9AF9A /* XMLReader.m in Sources */ = {isa = PBXBuildFile; fileRef = B76E094D1E768CE100A9AF9A /* XMLReader.m */; }; 13 | D935004D1D513CF700C7BA47 /* OAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D935004C1D513CF700C7BA47 /* OAuthManager.m */; }; 14 | D9F2EAD31DA9A9650000BD52 /* OAuthClient.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EAD11DA9A9650000BD52 /* OAuthClient.h */; }; 15 | D9F2EAD41DA9A9650000BD52 /* OAuthClient.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F2EAD21DA9A9650000BD52 /* OAuthClient.m */; }; 16 | D9F2EAD91DA9A9730000BD52 /* OAuth1Client.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EAD51DA9A9730000BD52 /* OAuth1Client.h */; }; 17 | D9F2EADA1DA9A9730000BD52 /* OAuth1Client.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F2EAD61DA9A9730000BD52 /* OAuth1Client.m */; }; 18 | D9F2EADB1DA9A9730000BD52 /* OAuth2Client.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EAD71DA9A9730000BD52 /* OAuth2Client.h */; }; 19 | D9F2EADC1DA9A9730000BD52 /* OAuth2Client.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F2EAD81DA9A9730000BD52 /* OAuth2Client.m */; }; 20 | D9F2EADE1DA9A9840000BD52 /* OAuthClientProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EADD1DA9A9840000BD52 /* OAuthClientProtocol.h */; }; 21 | D9F2EAE21DA9A9930000BD52 /* OAuthManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EADF1DA9A9930000BD52 /* OAuthManager.h */; }; 22 | D9F2EAE31DA9A9930000BD52 /* OAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F2EAE01DA9A9930000BD52 /* OAuthManager.m */; }; 23 | D9F2EAE41DA9A9930000BD52 /* OAuthManagerConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F2EAE11DA9A9930000BD52 /* OAuthManagerConstants.h */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | D902863D1D1D0106006B2E5B /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = D991AB771D1B237400DE9E58 /* Pods.xcodeproj */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 45935B91DF0442390E2CA6DEEF68AE99; 32 | remoteInfo = OAuthSwift; 33 | }; 34 | D9841C461D4A7ECD00C9BEA8 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = D991AB771D1B237400DE9E58 /* Pods.xcodeproj */; 37 | proxyType = 1; 38 | remoteGlobalIDString = F1B81AD95493A7C44AA1914D8FCA4531; 39 | remoteInfo = "Pods-OAuthManager"; 40 | }; 41 | D991AB7C1D1B237400DE9E58 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = D991AB771D1B237400DE9E58 /* Pods.xcodeproj */; 44 | proxyType = 2; 45 | remoteGlobalIDString = CAC5266A4EA1F9CFED6713A2C58E445D; 46 | remoteInfo = OAuthSwift; 47 | }; 48 | D991AB7E1D1B237400DE9E58 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = D991AB771D1B237400DE9E58 /* Pods.xcodeproj */; 51 | proxyType = 2; 52 | remoteGlobalIDString = 47C05594527CA5E982EE8A3D06C34291; 53 | remoteInfo = "Pods-OAuthManager"; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXCopyFilesBuildPhase section */ 58 | 58B511D91A9E6C8500147676 /* Copy Files */ = { 59 | isa = PBXCopyFilesBuildPhase; 60 | buildActionMask = 12; 61 | dstPath = ""; 62 | dstSubfolderSpec = 10; 63 | files = ( 64 | ); 65 | name = "Copy Files"; 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXCopyFilesBuildPhase section */ 69 | 70 | /* Begin PBXFileReference section */ 71 | B76E094B1E768CE100A9AF9A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 72 | B76E094C1E768CE100A9AF9A /* XMLReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLReader.h; sourceTree = ""; }; 73 | B76E094D1E768CE100A9AF9A /* XMLReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLReader.m; sourceTree = ""; }; 74 | D91353961DA7849100AABC96 /* libOAuthManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOAuthManager.a; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | D935004C1D513CF700C7BA47 /* OAuthManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OAuthManager.m; path = OAuthManager/OAuthManager.m; sourceTree = ""; }; 76 | D991AB771D1B237400DE9E58 /* Pods.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Pods.xcodeproj; path = Pods/Pods.xcodeproj; sourceTree = ""; }; 77 | D9F2EAD11DA9A9650000BD52 /* OAuthClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuthClient.h; path = OAuthManager/OAuthClient.h; sourceTree = SOURCE_ROOT; }; 78 | D9F2EAD21DA9A9650000BD52 /* OAuthClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OAuthClient.m; path = OAuthManager/OAuthClient.m; sourceTree = SOURCE_ROOT; }; 79 | D9F2EAD51DA9A9730000BD52 /* OAuth1Client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuth1Client.h; path = OAuthManager/OAuth1Client.h; sourceTree = SOURCE_ROOT; }; 80 | D9F2EAD61DA9A9730000BD52 /* OAuth1Client.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OAuth1Client.m; path = OAuthManager/OAuth1Client.m; sourceTree = SOURCE_ROOT; }; 81 | D9F2EAD71DA9A9730000BD52 /* OAuth2Client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuth2Client.h; path = OAuthManager/OAuth2Client.h; sourceTree = SOURCE_ROOT; }; 82 | D9F2EAD81DA9A9730000BD52 /* OAuth2Client.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OAuth2Client.m; path = OAuthManager/OAuth2Client.m; sourceTree = SOURCE_ROOT; }; 83 | D9F2EADD1DA9A9840000BD52 /* OAuthClientProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuthClientProtocol.h; path = OAuthManager/OAuthClientProtocol.h; sourceTree = SOURCE_ROOT; }; 84 | D9F2EADF1DA9A9930000BD52 /* OAuthManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuthManager.h; path = OAuthManager/OAuthManager.h; sourceTree = SOURCE_ROOT; }; 85 | D9F2EAE01DA9A9930000BD52 /* OAuthManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OAuthManager.m; path = OAuthManager/OAuthManager.m; sourceTree = SOURCE_ROOT; }; 86 | D9F2EAE11DA9A9930000BD52 /* OAuthManagerConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OAuthManagerConstants.h; path = OAuthManager/OAuthManagerConstants.h; sourceTree = SOURCE_ROOT; }; 87 | /* End PBXFileReference section */ 88 | 89 | /* Begin PBXFrameworksBuildPhase section */ 90 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | B76E094A1E768CE100A9AF9A /* XMLReader */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | B76E094B1E768CE100A9AF9A /* README.md */, 104 | B76E094C1E768CE100A9AF9A /* XMLReader.h */, 105 | B76E094D1E768CE100A9AF9A /* XMLReader.m */, 106 | ); 107 | name = XMLReader; 108 | path = OAuthManager/XMLReader; 109 | sourceTree = SOURCE_ROOT; 110 | }; 111 | D93EF9941DA77CBB00EC55A0 /* /Users/auser/Development/react-native/mine/OAuthManager/ios/OAuthManager.xcodeproj */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | B76E094A1E768CE100A9AF9A /* XMLReader */, 115 | D9F2EADF1DA9A9930000BD52 /* OAuthManager.h */, 116 | D9F2EAE01DA9A9930000BD52 /* OAuthManager.m */, 117 | D9F2EAE11DA9A9930000BD52 /* OAuthManagerConstants.h */, 118 | D9F2EAD11DA9A9650000BD52 /* OAuthClient.h */, 119 | D9F2EAD21DA9A9650000BD52 /* OAuthClient.m */, 120 | D9F6DCB41DA8EFBC00D10561 /* Clients */, 121 | D9F6DCB51DA8EFC300D10561 /* Protocols */, 122 | D91353961DA7849100AABC96 /* libOAuthManager.a */, 123 | ); 124 | path = "/Users/auser/Development/react-native/mine/OAuthManager/ios/OAuthManager.xcodeproj"; 125 | sourceTree = ""; 126 | }; 127 | D991AB781D1B237400DE9E58 /* Products */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | D991AB7D1D1B237400DE9E58 /* OAuthSwift.framework */, 131 | D991AB7F1D1B237400DE9E58 /* Pods_OAuthManager.framework */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | D9F6DCB41DA8EFBC00D10561 /* Clients */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | D9F2EAD51DA9A9730000BD52 /* OAuth1Client.h */, 140 | D9F2EAD61DA9A9730000BD52 /* OAuth1Client.m */, 141 | D9F2EAD71DA9A9730000BD52 /* OAuth2Client.h */, 142 | D9F2EAD81DA9A9730000BD52 /* OAuth2Client.m */, 143 | ); 144 | name = Clients; 145 | sourceTree = ""; 146 | }; 147 | D9F6DCB51DA8EFC300D10561 /* Protocols */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | D9F2EADD1DA9A9840000BD52 /* OAuthClientProtocol.h */, 151 | ); 152 | name = Protocols; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXHeadersBuildPhase section */ 158 | D92A80BB1DA9030200EFDC2C /* Headers */ = { 159 | isa = PBXHeadersBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | D9F2EAD91DA9A9730000BD52 /* OAuth1Client.h in Headers */, 163 | D9F2EADE1DA9A9840000BD52 /* OAuthClientProtocol.h in Headers */, 164 | B76E094F1E768CE100A9AF9A /* XMLReader.h in Headers */, 165 | D9F2EADB1DA9A9730000BD52 /* OAuth2Client.h in Headers */, 166 | D9F2EAD31DA9A9650000BD52 /* OAuthClient.h in Headers */, 167 | D9F2EAE41DA9A9930000BD52 /* OAuthManagerConstants.h in Headers */, 168 | D9F2EAE21DA9A9930000BD52 /* OAuthManager.h in Headers */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXHeadersBuildPhase section */ 173 | 174 | /* Begin PBXNativeTarget section */ 175 | 58B511DA1A9E6C8500147676 /* OAuthManager */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "OAuthManager" */; 178 | buildPhases = ( 179 | 58B511D71A9E6C8500147676 /* Sources */, 180 | 58B511D81A9E6C8500147676 /* Frameworks */, 181 | 58B511D91A9E6C8500147676 /* Copy Files */, 182 | D92A80BB1DA9030200EFDC2C /* Headers */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | D9841C471D4A7ECD00C9BEA8 /* PBXTargetDependency */, 188 | D902863E1D1D0106006B2E5B /* PBXTargetDependency */, 189 | ); 190 | name = OAuthManager; 191 | productName = RCTDataManager; 192 | productReference = D91353961DA7849100AABC96 /* libOAuthManager.a */; 193 | productType = "com.apple.product-type.library.static"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 58B511D31A9E6C8500147676 /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastUpgradeCheck = 0720; 202 | ORGANIZATIONNAME = Facebook; 203 | TargetAttributes = { 204 | 58B511DA1A9E6C8500147676 = { 205 | CreatedOnToolsVersion = 6.1.1; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "OAuthManager" */; 210 | compatibilityVersion = "Xcode 3.2"; 211 | developmentRegion = English; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | ); 216 | mainGroup = D93EF9941DA77CBB00EC55A0 /* /Users/auser/Development/react-native/mine/OAuthManager/ios/OAuthManager.xcodeproj */; 217 | productRefGroup = D93EF9941DA77CBB00EC55A0 /* /Users/auser/Development/react-native/mine/OAuthManager/ios/OAuthManager.xcodeproj */; 218 | projectDirPath = ""; 219 | projectReferences = ( 220 | { 221 | ProductGroup = D991AB781D1B237400DE9E58 /* Products */; 222 | ProjectRef = D991AB771D1B237400DE9E58 /* Pods.xcodeproj */; 223 | }, 224 | ); 225 | projectRoot = ""; 226 | targets = ( 227 | 58B511DA1A9E6C8500147676 /* OAuthManager */, 228 | ); 229 | }; 230 | /* End PBXProject section */ 231 | 232 | /* Begin PBXReferenceProxy section */ 233 | D991AB7D1D1B237400DE9E58 /* OAuthSwift.framework */ = { 234 | isa = PBXReferenceProxy; 235 | fileType = wrapper.framework; 236 | path = OAuthSwift.framework; 237 | remoteRef = D991AB7C1D1B237400DE9E58 /* PBXContainerItemProxy */; 238 | sourceTree = BUILT_PRODUCTS_DIR; 239 | }; 240 | D991AB7F1D1B237400DE9E58 /* Pods_OAuthManager.framework */ = { 241 | isa = PBXReferenceProxy; 242 | fileType = wrapper.framework; 243 | path = Pods_OAuthManager.framework; 244 | remoteRef = D991AB7E1D1B237400DE9E58 /* PBXContainerItemProxy */; 245 | sourceTree = BUILT_PRODUCTS_DIR; 246 | }; 247 | /* End PBXReferenceProxy section */ 248 | 249 | /* Begin PBXSourcesBuildPhase section */ 250 | 58B511D71A9E6C8500147676 /* Sources */ = { 251 | isa = PBXSourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | B76E094E1E768CE100A9AF9A /* README.md in Sources */, 255 | D9F2EADC1DA9A9730000BD52 /* OAuth2Client.m in Sources */, 256 | D935004D1D513CF700C7BA47 /* OAuthManager.m in Sources */, 257 | D9F2EAD41DA9A9650000BD52 /* OAuthClient.m in Sources */, 258 | D9F2EADA1DA9A9730000BD52 /* OAuth1Client.m in Sources */, 259 | D9F2EAE31DA9A9930000BD52 /* OAuthManager.m in Sources */, 260 | B76E09501E768CE100A9AF9A /* XMLReader.m in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXSourcesBuildPhase section */ 265 | 266 | /* Begin PBXTargetDependency section */ 267 | D902863E1D1D0106006B2E5B /* PBXTargetDependency */ = { 268 | isa = PBXTargetDependency; 269 | name = OAuthSwift; 270 | targetProxy = D902863D1D1D0106006B2E5B /* PBXContainerItemProxy */; 271 | }; 272 | D9841C471D4A7ECD00C9BEA8 /* PBXTargetDependency */ = { 273 | isa = PBXTargetDependency; 274 | name = "Pods-OAuthManager"; 275 | targetProxy = D9841C461D4A7ECD00C9BEA8 /* PBXContainerItemProxy */; 276 | }; 277 | /* End PBXTargetDependency section */ 278 | 279 | /* Begin XCBuildConfiguration section */ 280 | 58B511ED1A9E6C8500147676 /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BOOL_CONVERSION = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | COPY_PHASE_STRIP = NO; 298 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 299 | ENABLE_BITCODE = YES; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_TESTABILITY = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | HEADER_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | "$(SRCROOT)/../../node_modules/react-native/React/**", 319 | "$(SRCROOT)/../../react-native/React/**", 320 | ); 321 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 322 | MTL_ENABLE_DEBUG_INFO = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | OTHER_LDFLAGS = ( 325 | "$(inherited)", 326 | "-lc++", 327 | "-ObjC", 328 | ); 329 | SDKROOT = iphoneos; 330 | }; 331 | name = Debug; 332 | }; 333 | 58B511EE1A9E6C8500147676 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | COPY_PHASE_STRIP = YES; 351 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 352 | ENABLE_BITCODE = YES; 353 | ENABLE_NS_ASSERTIONS = NO; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | HEADER_SEARCH_PATHS = ( 363 | "$(inherited)", 364 | "$(SRCROOT)/../../node_modules/react-native/React/**", 365 | "$(SRCROOT)/../../react-native/React/**", 366 | ); 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 368 | MTL_ENABLE_DEBUG_INFO = NO; 369 | OTHER_LDFLAGS = ( 370 | "$(inherited)", 371 | "-lc++", 372 | "-ObjC", 373 | ); 374 | SDKROOT = iphoneos; 375 | VALIDATE_PRODUCT = YES; 376 | }; 377 | name = Release; 378 | }; 379 | 58B511F01A9E6C8500147676 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 383 | DYLIB_INSTALL_NAME_BASE = ""; 384 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 385 | ENABLE_BITCODE = YES; 386 | HEADER_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "$(SRCROOT)/../../React/**", 389 | "$(SRCROOT)/../../react-native/React/**", 390 | "$(SRCROOT)/../../react-native/Libraries/LinkingIOS", 391 | "$(SRCROOT)/../../../ios/Pods/DCTAuth/DCTAuth", 392 | ); 393 | LD_DYLIB_INSTALL_NAME = ""; 394 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 395 | MACH_O_TYPE = staticlib; 396 | OTHER_LDFLAGS = ( 397 | "$(inherited)", 398 | "-lReact", 399 | "-lRCTLinking", 400 | ); 401 | PRODUCT_NAME = OAuthManager; 402 | SKIP_INSTALL = YES; 403 | }; 404 | name = Debug; 405 | }; 406 | 58B511F11A9E6C8500147676 /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 410 | DYLIB_INSTALL_NAME_BASE = ""; 411 | EMBEDDED_CONTENT_CONTAINS_SWIFT = NO; 412 | ENABLE_BITCODE = YES; 413 | HEADER_SEARCH_PATHS = ( 414 | "$(inherited)", 415 | "$(SRCROOT)/../../React/**", 416 | "$(SRCROOT)/../../react-native/React/**", 417 | "$(SRCROOT)/../../react-native/Libraries/LinkingIOS", 418 | "$(SRCROOT)/../../../ios/Pods/DCTAuth/DCTAuth", 419 | ); 420 | LD_DYLIB_INSTALL_NAME = ""; 421 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 422 | MACH_O_TYPE = staticlib; 423 | OTHER_LDFLAGS = ( 424 | "$(inherited)", 425 | "-lReact", 426 | "-lRCTLinking", 427 | ); 428 | PRODUCT_NAME = OAuthManager; 429 | SKIP_INSTALL = YES; 430 | }; 431 | name = Release; 432 | }; 433 | /* End XCBuildConfiguration section */ 434 | 435 | /* Begin XCConfigurationList section */ 436 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "OAuthManager" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | 58B511ED1A9E6C8500147676 /* Debug */, 440 | 58B511EE1A9E6C8500147676 /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "OAuthManager" */ = { 446 | isa = XCConfigurationList; 447 | buildConfigurations = ( 448 | 58B511F01A9E6C8500147676 /* Debug */, 449 | 58B511F11A9E6C8500147676 /* Release */, 450 | ); 451 | defaultConfigurationIsVisible = 0; 452 | defaultConfigurationName = Release; 453 | }; 454 | /* End XCConfigurationList section */ 455 | }; 456 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 457 | } 458 | -------------------------------------------------------------------------------- /ios/OAuthManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/OAuthManager.xcodeproj/project.xcworkspace/xcuserdata/auser.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/ios/OAuthManager.xcodeproj/project.xcworkspace/xcuserdata/auser.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/OAuthManager.xcodeproj/xcuserdata/auser.xcuserdatad/xcschemes/OAuthManager.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/OAuthManager.xcodeproj/xcuserdata/auser.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | OAuthManager.xcscheme 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuth1Client.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthClient.h 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/7/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "OAuthManager.h" 11 | #import "OAuthManagerConstants.h" 12 | #import "OAuthClient.h" 13 | 14 | @interface OAuth1Client : OAuthClient 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuth1Client.m: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth1Client.m 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/7/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "OAuth1Client.h" 10 | #import "OAuthManager.h" 11 | #import "DCTAuth.h" 12 | 13 | static NSString *TAG = @"OAuth1Client"; 14 | 15 | @implementation OAuth1Client 16 | 17 | - (void) authorizeWithUrl:(NSString *)providerName 18 | url:(NSString *) url 19 | cfg:(NSDictionary *)cfg 20 | onSuccess:(AuthManagerCompletionBlock) onSuccess 21 | onError:(AuthManagerErrorBlock) onError 22 | { 23 | if (cfg == nil) { 24 | NSError *err = QUICK_ERROR(E_PROVIDER_NOT_CONFIGURED, @"provider not configured"); 25 | onError(err); 26 | return; 27 | } 28 | 29 | DCTOAuth1Account *account = [self getAccount:providerName cfg:cfg]; 30 | account.callbackURL = [NSURL URLWithString:url]; 31 | 32 | __weak id client = self; 33 | [account authenticateWithHandler:^(NSArray *responses, NSError *error) { 34 | if (error != nil) { 35 | NSString *response = ((DCTAuthResponse *)responses[0]).responseDescription; 36 | NSError *err = [NSError errorWithDomain:error.domain 37 | code:error.code 38 | userInfo:@{@"response": response}]; 39 | onError(err); 40 | return; 41 | } 42 | 43 | [client clearPendingAccount]; 44 | 45 | if (!account.authorized) { 46 | NSError *err = QUICK_ERROR(E_ACCOUNT_NOT_AUTHORIZED, @"account not authorized"); 47 | onError(err); 48 | return; 49 | } 50 | 51 | onSuccess(account); 52 | }]; 53 | return; 54 | } 55 | 56 | #pragma mark - Private 57 | 58 | - (DCTOAuth1Account *) getAccount:(NSString *)providerName 59 | cfg:(NSDictionary *)cfg 60 | { 61 | NSURL *request_token_url = [cfg objectForKey:@"request_token_url"]; 62 | NSURL *authorize_url = [cfg objectForKey:@"authorize_url"]; 63 | NSURL *access_token_url = [cfg objectForKey:@"access_token_url"]; 64 | NSString *key = [cfg valueForKey:@"consumer_key"]; 65 | NSString *secret = [cfg valueForKey:@"consumer_secret"]; 66 | 67 | NSString *signatureTypeStr = [cfg valueForKey:@"signatureType"]; 68 | NSString *parameterTransmisionStr = [cfg valueForKey:@"parameterTransmission"]; 69 | 70 | DCTOAuth1SignatureType signatureType = DCTOAuth1SignatureTypeHMAC_SHA1; 71 | DCTOAuth1ParameterTransmission parameterTransmission = DCTOAuth1ParameterTransmissionAuthorizationHeader; 72 | 73 | if (signatureTypeStr != nil && 74 | [signatureTypeStr compare:@"plaintext" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 75 | signatureType = DCTOAuth1SignatureTypePlaintext; 76 | } 77 | 78 | if (parameterTransmisionStr != nil && 79 | [parameterTransmisionStr compare:@"query_string" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 80 | parameterTransmission = DCTOAuth1ParameterTransmissionURLQuery; 81 | } 82 | 83 | DCTOAuth1Account *account = [[DCTOAuth1Account alloc] initWithType:providerName 84 | requestTokenURL:request_token_url 85 | authorizeURL:authorize_url 86 | accessTokenURL:access_token_url 87 | consumerKey:key 88 | consumerSecret:secret 89 | signatureType:signatureType 90 | parameterTransmission:parameterTransmission]; 91 | 92 | [self savePendingAccount:account]; 93 | return account; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuth2Client.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth2Client.h 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/7/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "OAuthManagerConstants.h" 11 | #import "OAuthClient.h" 12 | 13 | @interface OAuth2Client : OAuthClient 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuth2Client.m: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth2Client.m 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/7/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "OAuth2Client.h" 10 | #import "OAuthManager.h" 11 | #import "DCTAuth.h" 12 | 13 | static NSString *TAG = @"OAuth2Client"; 14 | 15 | @implementation OAuth2Client 16 | 17 | - (void) authorizeWithUrl:(NSString *)providerName 18 | url:(NSString *) url 19 | cfg:(NSDictionary *)cfg 20 | onSuccess:(AuthManagerCompletionBlock) onSuccess 21 | onError:(AuthManagerErrorBlock) onError 22 | { 23 | if (cfg == nil) { 24 | NSError *err = QUICK_ERROR(E_PROVIDER_NOT_CONFIGURED, @"provider not configured"); 25 | onError(err); 26 | return; 27 | } 28 | 29 | DCTOAuth2Account *account = [self getAccount:providerName cfg:cfg]; 30 | account.callbackURL = [NSURL URLWithString:url]; 31 | 32 | __weak OAuth2Client *client = self; 33 | NSLog(@"authorize ----> %@ %@", cfg, account); 34 | // authorizeWithClientID 35 | [account authenticateWithHandler:^(NSArray *responses, NSError *error) { 36 | NSLog(@"authenticateWithHandler: %@", responses); 37 | 38 | if (error != nil) { 39 | NSString *response = ((DCTAuthResponse *)responses[0]).responseDescription; 40 | NSError *err = [NSError errorWithDomain:error.domain 41 | code:error.code 42 | userInfo:@{@"response": response}]; 43 | onError(err); 44 | return; 45 | } 46 | 47 | [client clearPendingAccount]; 48 | 49 | if (!account.authorized) { 50 | NSError *err = QUICK_ERROR(E_ACCOUNT_NOT_AUTHORIZED, @"account not authorized"); 51 | NSLog(@"Account not authorized: %@", account); 52 | onError(err); 53 | return; 54 | } 55 | 56 | NSLog(@"Success!: %@", account); 57 | onSuccess(account); 58 | }]; 59 | } 60 | 61 | - (void) reauthenticateWithHandler:(NSString *) providerName 62 | cfg:(NSDictionary *)cfg 63 | onSuccess:(AuthManagerCompletionBlock) onSuccess 64 | onError:(AuthManagerErrorBlock) onError 65 | { 66 | DCTOAuth2Account *account = [self getAccount:providerName cfg:cfg]; 67 | [account reauthenticateWithHandler:^(DCTAuthResponse *response, NSError *error) { 68 | NSLog(@"Reauthenticating..."); 69 | if (error != nil) { 70 | NSError *err = [NSError errorWithDomain:error.domain 71 | code:error.code 72 | userInfo:@{@"response": response.responseDescription}]; 73 | onError(err); 74 | return; 75 | } 76 | 77 | if (!account.authorized) { 78 | NSError *err = QUICK_ERROR(E_ACCOUNT_NOT_AUTHORIZED, @"account not authorized"); 79 | onError(err); 80 | return; 81 | } 82 | 83 | onSuccess(account); 84 | }]; 85 | } 86 | 87 | #pragma mark - private 88 | 89 | - (DCTOAuth2Account *) getAccount:(NSString *)providerName 90 | cfg:(NSDictionary *)cfg 91 | { 92 | DCTOAuth2Account *account; 93 | // Required 94 | NSURL *authorize_url = [cfg objectForKey:@"authorize_url"]; 95 | NSString *scopeStr = [cfg valueForKey:@"scopes"]; 96 | // NSArray *scopes = [scopeStr componentsSeparatedByString:@","]; 97 | 98 | NSString *sep = @", "; 99 | NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:sep]; 100 | NSArray *scopes = [scopeStr componentsSeparatedByCharactersInSet:set]; 101 | 102 | // Optional 103 | 104 | NSURL *access_token_url = [cfg objectForKey:@"access_token_url"]; 105 | NSString *clientID = [cfg valueForKey:@"client_id"]; 106 | NSString *clientSecret = [cfg valueForKey:@"client_secret"]; 107 | NSString *username = [cfg valueForKey:@"username"]; 108 | NSString *password = [cfg valueForKey:@"password"]; 109 | 110 | NSLog(@"getAccount config: %@", cfg); 111 | if (access_token_url != nil) { 112 | account = [[DCTOAuth2Account alloc] initWithType:providerName 113 | authorizeURL:authorize_url 114 | accessTokenURL:access_token_url 115 | clientID:clientID 116 | clientSecret:clientSecret 117 | scopes:scopes]; 118 | } else { 119 | account = [[DCTOAuth2Account alloc] initWithType:providerName 120 | authorizeURL:authorize_url 121 | clientID:clientID 122 | clientSecret:clientSecret 123 | username:username 124 | password:password 125 | scopes:scopes]; 126 | } 127 | 128 | [self savePendingAccount:account]; 129 | 130 | return account; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthClient.h 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/8/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "OAuthClientProtocol.h" 11 | #import "DCTAuth.h" 12 | 13 | @interface OAuthClient : NSObject 14 | 15 | - (void) cancelAuthentication; 16 | - (void) savePendingAccount:(DCTAuthAccount *) account; 17 | - (void) clearPendingAccount; 18 | 19 | @property (nonatomic, weak) DCTAuthAccount *account; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthClient.m 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/8/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #import "OAuthClient.h" 10 | 11 | @implementation OAuthClient 12 | 13 | - (void) authorizeWithUrl:(NSString *)providerName 14 | url:(NSString *) url 15 | cfg:(NSDictionary *)cfg 16 | onSuccess:(AuthManagerCompletionBlock) onSuccess 17 | onError:(AuthManagerErrorBlock) onError 18 | { 19 | NSLog(@"Not implemented in here. Wrong class!"); 20 | } 21 | 22 | - (void) reauthenticateWithHandler:(NSString *) providerName 23 | cfg:(NSDictionary *)cfg 24 | onSuccess:(AuthManagerCompletionBlock) onSuccess 25 | onError:(AuthManagerErrorBlock) onError 26 | { 27 | NSLog(@"Not implemented in here. Wrong class!"); 28 | } 29 | 30 | - (void) cancelAuthentication 31 | { 32 | if (_account != nil) { 33 | @try { 34 | [_account cancelAuthentication]; 35 | } 36 | @catch (NSException *exception) { 37 | NSLog(@"An exception occurred while cancelling authentication: %@", [exception reason]); 38 | } 39 | } 40 | } 41 | 42 | #pragma mark - Helpers 43 | 44 | - (void) savePendingAccount:(DCTAuthAccount *) account 45 | { 46 | _account = account; 47 | } 48 | 49 | - (void) clearPendingAccount 50 | { 51 | NSLog(@"called clearPendingAccount: %@", _account); 52 | [_account cancelAuthentication]; 53 | _account = nil; 54 | } 55 | 56 | - (void (^)(DCTAuthResponse *response, NSError *error)) getHandler:(DCTAuthAccount *) account 57 | onSuccess:(AuthManagerCompletionBlock) onSuccess 58 | onError:(AuthManagerErrorBlock) onError 59 | { 60 | return ^(DCTAuthResponse *response, NSError *error) { 61 | NSLog(@"Reauthenticating..."); 62 | if (error != nil) { 63 | onError(error); 64 | return; 65 | } 66 | 67 | if (!account.authorized) { 68 | NSError *err = QUICK_ERROR(E_ACCOUNT_NOT_AUTHORIZED, @"account not authorized"); 69 | onError(err); 70 | return; 71 | } 72 | 73 | onSuccess(account); 74 | }; 75 | } 76 | 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthClientProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthClientProtocol.h 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/8/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef OAuthClientProtocol_h 10 | #define OAuthClientProtocol_h 11 | 12 | #import "OAuthManagerConstants.h" 13 | 14 | @protocol OAuthClientProtocol 15 | 16 | - (void) authorizeWithUrl:(NSString *)providerName 17 | url:(NSString *) url 18 | cfg:(NSDictionary *)cfg 19 | onSuccess:(AuthManagerCompletionBlock) onSuccess 20 | onError:(AuthManagerErrorBlock) onError; 21 | 22 | - (void) reauthenticateWithHandler:(NSString *) providerName 23 | cfg:(NSDictionary *)cfg 24 | onSuccess:(AuthManagerCompletionBlock) onSuccess 25 | onError:(AuthManagerErrorBlock) onError; 26 | 27 | @end 28 | 29 | #endif /* OAuthClientProtocol_h */ 30 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthManager.h 3 | // 4 | // Created by Ari Lerner on 5/31/16. 5 | // Copyright © 2016 Facebook. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #if __has_include() 11 | #import 12 | #else 13 | #import "RCTBridgeModule.h" 14 | #endif 15 | 16 | #if __has_include() 17 | #import 18 | #else 19 | #import "RCTLinkingManager.h" 20 | #endif 21 | 22 | 23 | 24 | @class OAuthClient; 25 | 26 | static NSString *kAuthConfig = @"OAuthManager"; 27 | 28 | @interface OAuthManager : NSObject 29 | 30 | + (instancetype) sharedManager; 31 | + (BOOL)setupOAuthHandler:(UIApplication *)application; 32 | 33 | + (BOOL)handleOpenUrl:(UIApplication *)application openURL:(NSURL *)url 34 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation; 35 | 36 | - (BOOL) _configureProvider:(NSString *) name andConfig:(NSDictionary *) config; 37 | - (NSDictionary *) getConfigForProvider:(NSString *)name; 38 | 39 | @property (nonatomic, strong) NSDictionary *providerConfig; 40 | @property (nonatomic, strong) NSArray *callbackUrls; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthManager.m 3 | // Created by Ari Lerner on 5/31/16. 4 | // Copyright © 2016 Facebook. All rights reserved. 5 | // 6 | 7 | #import 8 | #import 9 | #import 10 | #import 11 | 12 | #import "OAuthManager.h" 13 | #import "DCTAuth.h" 14 | #import "DCTAuthAccountStore.h" 15 | 16 | #import "OAuthClient.h" 17 | #import "OAuth1Client.h" 18 | #import "OAuth2Client.h" 19 | #import "XMLReader.h" 20 | 21 | @interface OAuthManager() 22 | @property (nonatomic) NSArray *pendingClients; 23 | @property BOOL pendingAuthentication; 24 | @end 25 | 26 | @implementation OAuthManager 27 | 28 | @synthesize callbackUrls = _callbackUrls; 29 | 30 | static NSString *const AUTH_MANAGER_TAG = @"AUTH_MANAGER"; 31 | static OAuthManager *manager; 32 | static dispatch_once_t onceToken; 33 | static SFSafariViewController *safariViewController = nil; 34 | 35 | RCT_EXPORT_MODULE(OAuthManager); 36 | 37 | // Run on a different thread 38 | - (dispatch_queue_t)methodQueue 39 | { 40 | return dispatch_queue_create("io.fullstack.oauth", DISPATCH_QUEUE_SERIAL); 41 | } 42 | 43 | + (instancetype)sharedManager { 44 | dispatch_once(&onceToken, ^{ 45 | manager = [self new]; 46 | }); 47 | return manager; 48 | } 49 | 50 | + (void) reset { 51 | onceToken = nil; 52 | manager = nil; 53 | } 54 | 55 | - (instancetype) init { 56 | self = [super init]; 57 | if (self != nil) { 58 | _callbackUrls = [[NSArray alloc] init]; 59 | _pendingClients = [[NSArray alloc] init]; 60 | 61 | [[NSNotificationCenter defaultCenter] addObserver:self 62 | selector:@selector(didBecomeActive:) 63 | name:UIApplicationDidBecomeActiveNotification 64 | object:nil]; 65 | 66 | } 67 | return self; 68 | } 69 | 70 | - (void) dealloc 71 | { 72 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 73 | } 74 | 75 | - (void) didBecomeActive:(NSNotification *)notification 76 | { 77 | // TODO? 78 | } 79 | 80 | /* 81 | Call this from your AppDelegate.h 82 | */ 83 | + (BOOL)setupOAuthHandler:(UIApplication *)application 84 | { 85 | OAuthManager *sharedManager = [OAuthManager sharedManager]; 86 | DCTAuthPlatform *authPlatform = [DCTAuthPlatform sharedPlatform]; 87 | 88 | [authPlatform setURLOpener: ^void(NSURL *URL, DCTAuthPlatformCompletion completion) { 89 | // [sharedManager setPendingAuthentication:YES]; 90 | if ([SFSafariViewController class] != nil) { 91 | dispatch_async(dispatch_get_main_queue(), ^{ 92 | safariViewController = [[SFSafariViewController alloc] initWithURL:URL]; 93 | UIViewController *viewController = application.keyWindow.rootViewController; 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | [viewController presentViewController:safariViewController animated:YES completion: nil]; 96 | }); 97 | }); 98 | } else { 99 | [application openURL:URL]; 100 | } 101 | completion(YES); 102 | }]; 103 | 104 | // Check for plist file 105 | NSString *path = [[NSBundle mainBundle] pathForResource:kAuthConfig ofType:@"plist"]; 106 | if (path != nil) { 107 | // plist exists 108 | NSDictionary *initialConfig = [NSDictionary dictionaryWithContentsOfFile:path]; 109 | for (NSString *name in [initialConfig allKeys]) { 110 | NSDictionary *cfg = [initialConfig objectForKey:name]; 111 | [sharedManager _configureProvider:name andConfig:cfg]; 112 | } 113 | } else { 114 | [sharedManager setProviderConfig:[NSDictionary dictionary]]; 115 | } 116 | 117 | return YES; 118 | } 119 | 120 | + (BOOL)handleOpenUrl:(UIApplication *)application openURL:(NSURL *)url 121 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 122 | { 123 | OAuthManager *manager = [OAuthManager sharedManager]; 124 | NSString *strUrl = [manager stringHost:url]; 125 | 126 | if ([manager.callbackUrls indexOfObject:strUrl] != NSNotFound) { 127 | if(safariViewController != nil) { 128 | [safariViewController dismissViewControllerAnimated:YES completion:nil]; 129 | } 130 | return [DCTAuth handleURL:url]; 131 | } 132 | 133 | // [manager clearPending]; 134 | 135 | return [RCTLinkingManager application:application openURL:url 136 | sourceApplication:sourceApplication annotation:annotation]; 137 | } 138 | 139 | - (BOOL) _configureProvider:(NSString *)providerName andConfig:(NSDictionary *)config 140 | { 141 | if (self.providerConfig == nil) { 142 | self.providerConfig = [[NSDictionary alloc] init]; 143 | } 144 | 145 | NSMutableDictionary *providerCfgs = [self.providerConfig mutableCopy]; 146 | 147 | NSMutableDictionary *objectProps = [[NSMutableDictionary alloc] init]; 148 | 149 | // Save the callback url for checking later 150 | NSMutableArray *arr = [_callbackUrls mutableCopy]; 151 | NSString *callbackUrlStr = [config valueForKey:@"callback_url"]; 152 | NSURL *callbackUrl = [NSURL URLWithString:callbackUrlStr]; 153 | NSString *saveCallbackUrl = [[self stringHost:callbackUrl] lowercaseString]; 154 | 155 | if ([arr indexOfObject:saveCallbackUrl] == NSNotFound) { 156 | [arr addObject:saveCallbackUrl]; 157 | _callbackUrls = [arr copy]; 158 | NSLog(@"Saved callback url: %@ in %@", saveCallbackUrl, _callbackUrls); 159 | } 160 | 161 | 162 | // Convert objects of url type 163 | for (NSString *name in [config allKeys]) { 164 | if ([name rangeOfString:@"_url"].location != NSNotFound) { 165 | // This is a URL representation 166 | NSString *urlStr = [config valueForKey:name]; 167 | NSURL *url = [NSURL URLWithString:urlStr]; 168 | [objectProps setObject:url forKey:name]; 169 | } else { 170 | NSString *str = [NSString stringWithString:[config valueForKey:name]]; 171 | [objectProps setValue:str forKey:name]; 172 | } 173 | } 174 | 175 | [providerCfgs setObject:objectProps forKey:providerName]; 176 | 177 | self.providerConfig = providerCfgs; 178 | 179 | return YES; 180 | } 181 | 182 | - (NSDictionary *) getConfigForProvider:(NSString *)name 183 | { 184 | return [self.providerConfig objectForKey:name]; 185 | } 186 | 187 | /** 188 | * configure provider 189 | * 190 | * @param {string} providerName - name of the provider we are configuring 191 | * @param [object] props - properties to set on the configuration object 192 | */ 193 | RCT_EXPORT_METHOD(configureProvider: 194 | (NSString *)providerName 195 | props:(NSDictionary *)props 196 | callback:(RCTResponseSenderBlock)callback) 197 | { 198 | OAuthManager *sharedManager = [OAuthManager sharedManager]; 199 | 200 | if ([sharedManager _configureProvider:providerName andConfig:props]) { 201 | callback(@[[NSNull null], @{ 202 | @"status": @"ok" 203 | }]); 204 | } else { 205 | // Error? 206 | callback(@[@{ 207 | @"status": @"error" 208 | }]); 209 | } 210 | } 211 | 212 | #pragma mark OAuth 213 | 214 | // TODO: Remove opts 215 | RCT_EXPORT_METHOD(getSavedAccounts:(NSDictionary *) opts 216 | callback:(RCTResponseSenderBlock) callback) 217 | { 218 | OAuthManager *manager = [OAuthManager sharedManager]; 219 | DCTAuthAccountStore *store = [manager accountStore]; 220 | 221 | NSSet *accounts = [store accounts]; 222 | NSMutableArray *respAccounts = [[NSMutableArray alloc] init]; 223 | for (DCTAuthAccount *account in [accounts allObjects]) { 224 | NSString *providerName = account.type; 225 | NSMutableDictionary *cfg = [[manager getConfigForProvider:providerName] mutableCopy]; 226 | NSMutableDictionary *acc = [[manager getAccountResponse:account cfg:cfg] mutableCopy]; 227 | [acc setValue:providerName forKey:@"provider"]; 228 | [respAccounts addObject:acc]; 229 | } 230 | callback(@[[NSNull null], @{ 231 | @"status": @"ok", 232 | @"accounts": respAccounts 233 | }]); 234 | } 235 | 236 | // TODO: Remove opts 237 | RCT_EXPORT_METHOD(getSavedAccount:(NSString *)providerName 238 | opts:(NSDictionary *) opts 239 | callback:(RCTResponseSenderBlock)callback) 240 | { 241 | OAuthManager *manager = [OAuthManager sharedManager]; 242 | NSMutableDictionary *cfg = [[manager getConfigForProvider:providerName] mutableCopy]; 243 | 244 | DCTAuthAccount *existingAccount = [manager accountForProvider:providerName]; 245 | if (existingAccount != nil) { 246 | if ([existingAccount isAuthorized]) { 247 | NSDictionary *accountResponse = [manager getAccountResponse:existingAccount cfg:cfg]; 248 | callback(@[[NSNull null], @{ 249 | @"status": @"ok", 250 | @"provider": providerName, 251 | @"response": accountResponse 252 | }]); 253 | return; 254 | } else { 255 | DCTAuthAccountStore *store = [manager accountStore]; 256 | [store deleteAccount:existingAccount]; 257 | NSDictionary *errResp = @{ 258 | @"status": @"error", 259 | @"response": @{ 260 | @"msg": @"Account not authorized" 261 | } 262 | }; 263 | callback(@[errResp]); 264 | } 265 | } else { 266 | NSDictionary *errResp = @{ 267 | @"status": @"error", 268 | @"response": @{ 269 | @"msg": @"No saved account" 270 | } 271 | }; 272 | callback(@[errResp]); 273 | } 274 | } 275 | 276 | RCT_EXPORT_METHOD(deauthorize:(NSString *) providerName 277 | callback:(RCTResponseSenderBlock) callback) 278 | { 279 | OAuthManager *manager = [OAuthManager sharedManager]; 280 | DCTAuthAccountStore *store = [manager accountStore]; 281 | 282 | DCTAuthAccount *existingAccount = [manager accountForProvider:providerName]; 283 | if (existingAccount != nil) { 284 | [store deleteAccount:existingAccount]; 285 | callback(@[[NSNull null], @{ 286 | @"status": @"ok" 287 | }]); 288 | } else { 289 | NSDictionary *resp = @{ 290 | @"status": @"error", 291 | @"msg": [NSString stringWithFormat:@"No account found for %@", providerName] 292 | }; 293 | callback(@[resp]); 294 | } 295 | } 296 | 297 | /** 298 | * authorize with url 299 | * provider, url, scope, state, params 300 | **/ 301 | RCT_EXPORT_METHOD(authorize:(NSString *)providerName 302 | opts:(NSDictionary *) opts 303 | callback:(RCTResponseSenderBlock)callback) 304 | { 305 | OAuthManager *manager = [OAuthManager sharedManager]; 306 | [manager clearPending]; 307 | NSMutableDictionary *cfg = [[manager getConfigForProvider:providerName] mutableCopy]; 308 | 309 | DCTAuthAccount *existingAccount = [manager accountForProvider:providerName]; 310 | NSString *clientID = ([providerName isEqualToString:@"google"]) ? ((DCTOAuth2Credential *) existingAccount).clientID : (NSString *)nil; 311 | 312 | if (([providerName isEqualToString:@"google"] && existingAccount && clientID != nil) 313 | || (![providerName isEqualToString:@"google"] && existingAccount != nil)) { 314 | if ([existingAccount isAuthorized]) { 315 | NSDictionary *accountResponse = [manager getAccountResponse:existingAccount cfg:cfg]; 316 | callback(@[[NSNull null], @{ 317 | @"status": @"ok", 318 | @"provider": providerName, 319 | @"response": accountResponse 320 | }]); 321 | return; 322 | } else { 323 | DCTAuthAccountStore *store = [manager accountStore]; 324 | [store deleteAccount:existingAccount]; 325 | } 326 | } 327 | 328 | NSString *callbackUrl; 329 | NSURL *storedCallbackUrl = [cfg objectForKey:@"callback_url"]; 330 | 331 | if (storedCallbackUrl != nil) { 332 | callbackUrl = [storedCallbackUrl absoluteString]; 333 | } else { 334 | NSString *appName = [cfg valueForKey:@"app_name"]; 335 | callbackUrl = [NSString 336 | stringWithFormat:@"%@://oauth-response/%@", 337 | appName, 338 | providerName]; 339 | } 340 | 341 | NSString *version = [cfg valueForKey:@"auth_version"]; 342 | [cfg addEntriesFromDictionary:opts]; 343 | 344 | OAuthClient *client; 345 | 346 | if ([version isEqualToString:@"1.0"]) { 347 | // OAuth 1 348 | client = (OAuthClient *)[[OAuth1Client alloc] init]; 349 | } else if ([version isEqualToString:@"2.0"]) { 350 | client = (OAuthClient *)[[OAuth2Client alloc] init]; 351 | } else { 352 | return callback(@[@{ 353 | @"status": @"error", 354 | @"msg": @"Unknown provider" 355 | }]); 356 | } 357 | 358 | // Store pending client 359 | 360 | [manager addPending:client]; 361 | _pendingAuthentication = YES; 362 | 363 | NSLog(@"Calling authorizeWithUrl: %@ with callbackURL: %@\n %@", providerName, callbackUrl, cfg); 364 | 365 | [client authorizeWithUrl:providerName 366 | url:callbackUrl 367 | cfg:cfg 368 | onSuccess:^(DCTAuthAccount *account) { 369 | NSLog(@"on success called with account: %@", account); 370 | NSDictionary *accountResponse = [manager getAccountResponse:account cfg:cfg]; 371 | _pendingAuthentication = NO; 372 | [manager removePending:client]; 373 | [[manager accountStore] saveAccount:account]; // <~ 374 | 375 | callback(@[[NSNull null], @{ 376 | @"status": @"ok", 377 | @"response": accountResponse 378 | }]); 379 | } onError:^(NSError *error) { 380 | NSLog(@"Error in authorizeWithUrl: %@", error); 381 | _pendingAuthentication = NO; 382 | callback(@[@{ 383 | @"status": @"error", 384 | @"msg": [error localizedDescription], 385 | @"userInfo": error.userInfo 386 | }]); 387 | [manager removePending:client]; 388 | }]; 389 | } 390 | 391 | RCT_EXPORT_METHOD(makeRequest:(NSString *)providerName 392 | urlOrPath:(NSString *) urlOrPath 393 | opts:(NSDictionary *) opts 394 | callback:(RCTResponseSenderBlock)callback) 395 | { 396 | OAuthManager *manager = [OAuthManager sharedManager]; 397 | NSMutableDictionary *cfg = [[manager getConfigForProvider:providerName] mutableCopy]; 398 | 399 | DCTAuthAccount *existingAccount = [manager accountForProvider:providerName]; 400 | if (existingAccount == nil) { 401 | NSDictionary *errResp = @{ 402 | @"status": @"error", 403 | @"msg": [NSString stringWithFormat:@"No account found for %@", providerName] 404 | }; 405 | callback(@[errResp]); 406 | return; 407 | } 408 | 409 | NSDictionary *creds = [self credentialForAccount:providerName cfg:cfg]; 410 | 411 | // If we have the http in the string, use it as the URL, otherwise create one 412 | // with the configuration 413 | NSURL *apiUrl; 414 | if ([urlOrPath hasPrefix:@"http"]) { 415 | apiUrl = [NSURL URLWithString:urlOrPath]; 416 | } else { 417 | NSURL *apiHost = [cfg objectForKey:@"api_url"]; 418 | apiUrl = [NSURL URLWithString:[[apiHost absoluteString] stringByAppendingString:urlOrPath]]; 419 | } 420 | 421 | // If there are params 422 | NSMutableArray *items = [NSMutableArray array]; 423 | NSDictionary *params = [opts objectForKey:@"params"]; 424 | if (params != nil) { 425 | for (NSString *key in params) { 426 | 427 | NSString *value = [params valueForKey:key]; 428 | 429 | if ([value isEqualToString:@"access_token"]) { 430 | value = [creds valueForKey:@"access_token"]; 431 | } 432 | 433 | NSURLQueryItem *item = [NSURLQueryItem queryItemWithName:key value:value]; 434 | 435 | if (item != nil) { 436 | [items addObject:item]; 437 | } 438 | } 439 | } 440 | 441 | NSString *methodStr = [opts valueForKey:@"method"]; 442 | 443 | DCTAuthRequestMethod method = [manager getRequestMethodByString:methodStr]; 444 | 445 | DCTAuthRequest *request = 446 | [[DCTAuthRequest alloc] 447 | initWithRequestMethod:method 448 | URL:apiUrl 449 | items:items]; 450 | 451 | // Allow json body in POST / PUT requests 452 | NSDictionary *body = [opts objectForKey:@"body"]; 453 | if (body != nil) { 454 | NSMutableArray *items = [NSMutableArray array]; 455 | 456 | for (NSString *key in body) { 457 | NSString *value = [body valueForKey:key]; 458 | 459 | DCTAuthContentItem *item = [[DCTAuthContentItem alloc] initWithName:key value:value]; 460 | 461 | if(item != nil) { 462 | [items addObject: item]; 463 | } 464 | } 465 | 466 | DCTAuthContent *content = [[DCTAuthContent alloc] initWithEncoding:NSUTF8StringEncoding 467 | type:DCTAuthContentTypeJSON 468 | items:items]; 469 | [request setContent:content]; 470 | } 471 | 472 | // If there are headers 473 | NSDictionary *headers = [opts objectForKey:@"headers"]; 474 | if (headers != nil) { 475 | NSMutableDictionary *existingHeaders = [request.HTTPHeaders mutableCopy]; 476 | for (NSString *header in headers) { 477 | [existingHeaders setValue:[headers valueForKey:header] forKey:header]; 478 | } 479 | request.HTTPHeaders = existingHeaders; 480 | } 481 | 482 | request.account = existingAccount; 483 | 484 | [request performRequestWithHandler:^(DCTAuthResponse *response, NSError *error) { 485 | if (error != nil) { 486 | NSDictionary *errorDict = @{ 487 | @"status": @"error", 488 | @"msg": [error localizedDescription] 489 | }; 490 | callback(@[errorDict]); 491 | } else { 492 | NSInteger statusCode = response.statusCode; 493 | NSData *rawData = response.data; 494 | NSDictionary *headers = response.HTTPHeaders; 495 | 496 | NSError *err; 497 | NSArray *data; 498 | 499 | 500 | 501 | // Check if returned data is a valid JSON 502 | // != nil returned if the rawdata is not a valid JSON 503 | if ((data = [NSJSONSerialization JSONObjectWithData:rawData 504 | options:kNilOptions 505 | error:&err]) == nil) { 506 | // Resetting err variable. 507 | err = nil; 508 | 509 | // Parse XML 510 | data = [XMLReader dictionaryForXMLData:rawData 511 | options:XMLReaderOptionsProcessNamespaces 512 | error:&err]; 513 | } 514 | if (err != nil) { 515 | NSDictionary *errResp = @{ 516 | @"status": @"error", 517 | @"msg": [NSString stringWithFormat:@"JSON parsing error: %@", [err localizedDescription]] 518 | }; 519 | callback(@[errResp]); 520 | } else { 521 | 522 | NSDictionary *resp = @{ 523 | @"status": @(statusCode), 524 | @"data": data != nil ? data : @[], 525 | @"headers": headers, 526 | }; 527 | callback(@[[NSNull null], resp]); 528 | } 529 | } 530 | }]; 531 | } 532 | 533 | #pragma mark - private 534 | 535 | - (DCTAuthAccount *) accountForProvider:(NSString *) providerName 536 | { 537 | DCTAuthAccountStore *store = [self accountStore]; 538 | NSSet *accounts = [store accountsWithType:providerName]; 539 | if ([accounts count] == 0) { 540 | return nil; 541 | } else { 542 | NSArray *allAccounts = [accounts allObjects]; 543 | if ([allAccounts count] == 0) { 544 | return nil; 545 | } else { 546 | return [allAccounts lastObject]; 547 | } 548 | } 549 | } 550 | 551 | - (NSDictionary *) credentialForAccount:(NSString *)providerName 552 | cfg:(NSDictionary *)cfg 553 | { 554 | DCTAuthAccount *account = [self accountForProvider:providerName]; 555 | if (!account) { 556 | return nil; 557 | } 558 | 559 | NSString *version = [cfg valueForKey:@"auth_version"]; 560 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; 561 | 562 | if ([version isEqualToString:@"1.0"]) { 563 | DCTOAuth1Credential *credentials = [account credential]; 564 | 565 | if (credentials) { 566 | if (credentials.oauthToken) { 567 | NSString *token = credentials.oauthToken; 568 | [dict setObject:token forKey:@"access_token"]; 569 | } 570 | 571 | if (credentials.oauthTokenSecret) { 572 | NSString *secret = credentials.oauthTokenSecret; 573 | [dict setObject:secret forKey:@"access_token_secret"]; 574 | } 575 | } 576 | 577 | } else if ([version isEqualToString:@"2.0"]) { 578 | DCTOAuth2Credential *credentials = [account credential]; 579 | 580 | if (credentials) { 581 | if (credentials.accessToken) { 582 | NSString *token = credentials.accessToken; 583 | [dict setObject:token forKey:@"access_token"]; 584 | } 585 | } 586 | } 587 | 588 | return dict; 589 | } 590 | 591 | - (DCTAuthRequestMethod) getRequestMethodByString:(NSString *) method 592 | { 593 | if ([method compare:@"get" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 594 | return DCTAuthRequestMethodGET; 595 | } else if ([method compare:@"post" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 596 | return DCTAuthRequestMethodPOST; 597 | } else if ([method compare:@"put" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 598 | return DCTAuthRequestMethodPUT; 599 | } else if ([method compare:@"delete" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 600 | return DCTAuthRequestMethodDELETE; 601 | } else if ([method compare:@"head" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 602 | return DCTAuthRequestMethodHEAD; 603 | } else if ([method compare:@"options" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 604 | return DCTAuthRequestMethodOPTIONS; 605 | } else if ([method compare:@"patch" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 606 | return DCTAuthRequestMethodPATCH; 607 | } else if ([method compare:@"trace" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 608 | return DCTAuthRequestMethodTRACE; 609 | } else { 610 | return DCTAuthRequestMethodGET; 611 | } 612 | } 613 | 614 | - (NSDictionary *) getAccountResponse:(DCTAuthAccount *) account 615 | cfg:(NSDictionary *)cfg 616 | { 617 | NSArray *ignoredCredentialProperties = @[@"superclass", @"hash", @"description", @"debugDescription"]; 618 | NSString *version = [cfg valueForKey:@"auth_version"]; 619 | NSMutableDictionary *accountResponse = [@{ 620 | @"authorized": @(account.authorized), 621 | @"uuid": account.identifier 622 | } mutableCopy]; 623 | 624 | if ([version isEqualToString:@"1.0"]) { 625 | DCTOAuth1Credential *credential = account.credential; 626 | if (credential != nil) { 627 | NSDictionary *cred = @{ 628 | @"access_token": credential.oauthToken, 629 | @"access_token_secret": credential.oauthTokenSecret 630 | }; 631 | [accountResponse setObject:cred forKey:@"credentials"]; 632 | } 633 | } else if ([version isEqualToString:@"2.0"]) { 634 | DCTOAuth2Credential *credential = account.credential; 635 | if (credential != nil) { 636 | 637 | NSMutableDictionary *cred = [self dictionaryForCredentialKeys: credential]; 638 | 639 | DCTOAuth2Account *oauth2Account = (DCTOAuth2Account *) account; 640 | if (oauth2Account.scopes) { 641 | [cred setObject:oauth2Account.scopes forKey:@"scopes"]; 642 | } 643 | 644 | [accountResponse setObject:cred forKey:@"credentials"]; 645 | } 646 | } 647 | [accountResponse setValue:[account identifier] forKey:@"identifier"]; 648 | if (account.userInfo != nil) { 649 | [accountResponse setObject:[account userInfo] forKey:@"user_info"]; 650 | } 651 | 652 | return accountResponse; 653 | } 654 | 655 | - (NSDictionary *) dictionaryForCredentialKeys:(NSObject *) credential 656 | { 657 | NSArray *ignoredCredentialProperties = @[@"superclass", @"hash", @"description", @"debugDescription"]; 658 | unsigned int count = 0; 659 | NSMutableDictionary *cred = [NSMutableDictionary new]; 660 | objc_property_t *properties = class_copyPropertyList([credential class], &count); 661 | 662 | for (int i = 0; i < count; i++) { 663 | 664 | NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])]; 665 | if ([ignoredCredentialProperties containsObject:key]) { 666 | NSLog(@"Ignoring credentials key: %@", key); 667 | } else { 668 | id value = [credential valueForKey:key]; 669 | 670 | if (value == nil) { 671 | 672 | } else if ([value isKindOfClass:[NSNumber class]] 673 | || [value isKindOfClass:[NSString class]] 674 | || [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) { 675 | // TODO: extend to other types 676 | [cred setObject:value forKey:key]; 677 | } else if ([value isKindOfClass:[NSObject class]]) { 678 | [cred setObject:[value dictionary] forKey:key]; 679 | } else { 680 | NSLog(@"Invalid type for %@ (%@)", NSStringFromClass([self class]), key); 681 | } 682 | } 683 | } 684 | return cred; 685 | } 686 | 687 | - (void) clearPending 688 | { 689 | OAuthManager *manager = [OAuthManager sharedManager]; 690 | for (OAuthClient *client in manager.pendingClients) { 691 | [manager removePending:client]; 692 | } 693 | manager.pendingClients = [NSArray array]; 694 | _pendingAuthentication = NO; 695 | } 696 | 697 | - (void) addPending:(OAuthClient *) client 698 | { 699 | OAuthManager *manager = [OAuthManager sharedManager]; 700 | NSMutableArray *newPendingClients = [manager.pendingClients mutableCopy]; 701 | [newPendingClients addObject:client]; 702 | manager.pendingClients = newPendingClients; 703 | } 704 | 705 | - (void) removePending:(OAuthClient *) client 706 | { 707 | [client clearPendingAccount]; 708 | OAuthManager *manager = [OAuthManager sharedManager]; 709 | NSUInteger idx = [manager.pendingClients indexOfObject:client]; 710 | if ([manager.pendingClients count] <= idx) { 711 | NSMutableArray *newPendingClients = [manager.pendingClients mutableCopy]; 712 | [newPendingClients removeObjectAtIndex:idx]; 713 | manager.pendingClients = newPendingClients; 714 | } 715 | } 716 | 717 | - (DCTAuthAccountStore *) accountStore 718 | { 719 | NSString *name = [NSString stringWithFormat:@"%@", AUTH_MANAGER_TAG]; 720 | return [DCTAuthAccountStore accountStoreWithName:name]; 721 | } 722 | 723 | - (NSString *) stringHost:(NSURL *)url 724 | { 725 | NSString *str; 726 | if (url.host != nil) { 727 | str = [NSString stringWithFormat:@"%@://%@%@", url.scheme, url.host, url.path]; 728 | } else { 729 | str = [NSString stringWithFormat:@"%@%@", url.scheme, url.path]; 730 | } 731 | 732 | if ([str hasSuffix:@"/"]) { 733 | str = [str substringToIndex:str.length - 1]; 734 | } 735 | 736 | return str; 737 | } 738 | 739 | @end 740 | 741 | -------------------------------------------------------------------------------- /ios/OAuthManager/OAuthManagerConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthManagerConstants.h 3 | // OAuthManager 4 | // 5 | // Created by Ari Lerner on 10/7/16. 6 | // Copyright © 2016 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef OAuthManagerConstants_h 10 | #define OAuthManagerConstants_h 11 | 12 | #import "DCTAuthAccount.h" 13 | 14 | enum _runtime_error 15 | { 16 | E_UNKNOWN_PROVIDER = 0, 17 | E_PROVIDER_NOT_CONFIGURED = 1, 18 | E_ACCOUNT_NOT_AUTHORIZED = 2 19 | } runtime_error_t; 20 | 21 | #define QUICK_ERROR(error_code, error_description) [NSError errorWithDomain:NSStringFromClass([self class]) code:error_code userInfo:[NSDictionary dictionaryWithObject:error_description forKey:NSLocalizedDescriptionKey]]; 22 | 23 | typedef void(^AuthManagerCompletionBlock)(DCTAuthAccount *account); 24 | typedef void(^AuthManagerErrorBlock)(NSError *error); 25 | 26 | #endif /* OAuthManagerConstants_h */ 27 | -------------------------------------------------------------------------------- /ios/OAuthManager/XMLReader/README.md: -------------------------------------------------------------------------------- 1 | # XMLReader 2 | 3 | This project comes from a component developed by Troy Brant and published on his website : http://troybrant.net/blog/2010/09/simple-xml-to-nsdictionary-converter/ 4 | 5 | I'm open sourcing some of the updates I've made on it. 6 | 7 | 8 | ## Usage 9 | 10 | NSData *data = ...; // some data that can be received from remote service 11 | NSError *error = nil; 12 | NSDictionary *dict = [XMLReader dictionaryForXMLData:data 13 | options:XMLReaderOptionsProcessNamespaces 14 | error:&error]; 15 | 16 | 17 | ## Requirements 18 | 19 | Xcode 4.4 and above because project use the "auto-synthesized property" feature. 20 | 21 | 22 | ## FAQ 23 | 24 | #### Sometimes I get an `NSDictionary` while I must get an `NSArray`, why ? 25 | 26 | In the algorithm of the `XMLReader`, when the parser found a new tag it automatically creates an `NSDictionary`, if it found another occurrence of the same tag at the same level in the XML tree it creates another dictionary and put both dictionaries inside an `NSArray`. 27 | 28 | The consequence is: if you have a list that contains only one item, you will get an `NSDictionary` as result and not an `NSArray`. 29 | The only workaround is to check the class of the object contained for in the dictionary using `isKindOfClass:`. See sample code below : 30 | 31 | NSData *data = ...; 32 | NSError *error = nil; 33 | NSDictionary *dict = [XMLReader dictionaryForXMLData:data error:&error]; 34 | 35 | NSArray *list = [dict objectForKey:@"list"]; 36 | if (![list isKindOfClass:[NSArray class]]) 37 | { 38 | // if 'list' isn't an array, we create a new array containing our object 39 | list = [NSArray arrayWithObject:list]; 40 | } 41 | 42 | // we can loop through items safely now 43 | for (NSDictionary *item in list) 44 | { 45 | // ... 46 | } 47 | 48 | 49 | #### I don't have enable ARC on my project, how can I use your library ? 50 | 51 | You have 2 options: 52 | 53 | * Use the branch "[no-objc-arc](https://github.com/amarcadet/XMLReader/tree/no-objc-arc)" that use manual reference counting. 54 | * **Better choice:** add the "-fobjc-arc" compiler flag on `XMLReader.m` file in your build phases. 55 | 56 | #### I have trust issues, I don't want ARC, I prefer MRC, what can I do ? 57 | 58 | Well, nobody is perfect but, still, you can use the branch "[no-objc-arc](https://github.com/amarcadet/XMLReader/tree/no-objc-arc)". 59 | 60 | 61 | ## Contributions 62 | 63 | Thanks to the original author of this component Troy Brant and to [Divan "snip3r8" Visagie](https://github.com/snip3r8) for providing ARC support. 64 | 65 | 66 | ## License 67 | 68 | Copyright (C) 2012 Antoine Marcadet 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 75 | 76 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/amarcadet/XMLReader/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 77 | 78 | -------------------------------------------------------------------------------- /ios/OAuthManager/XMLReader/XMLReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // XMLReader.h 3 | // 4 | // Created by Troy Brant on 9/18/10. 5 | // Updated by Antoine Marcadet on 9/23/11. 6 | // Updated by Divan Visagie on 2012-08-26 7 | // 8 | 9 | #import 10 | 11 | enum { 12 | XMLReaderOptionsProcessNamespaces = 1 << 0, // Specifies whether the receiver reports the namespace and the qualified name of an element. 13 | XMLReaderOptionsReportNamespacePrefixes = 1 << 1, // Specifies whether the receiver reports the scope of namespace declarations. 14 | XMLReaderOptionsResolveExternalEntities = 1 << 2, // Specifies whether the receiver reports declarations of external entities. 15 | }; 16 | typedef NSUInteger XMLReaderOptions; 17 | 18 | @interface XMLReader : NSObject 19 | 20 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)errorPointer; 21 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)errorPointer; 22 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)errorPointer; 23 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)errorPointer; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ios/OAuthManager/XMLReader/XMLReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // XMLReader.m 3 | // 4 | // Created by Troy Brant on 9/18/10. 5 | // Updated by Antoine Marcadet on 9/23/11. 6 | // Updated by Divan Visagie on 2012-08-26 7 | // 8 | 9 | #import "XMLReader.h" 10 | 11 | #if !defined(__has_feature) || !__has_feature(objc_arc) 12 | #error "XMLReader requires ARC support." 13 | #endif 14 | 15 | NSString *const kXMLReaderTextNodeKey = @"text"; 16 | NSString *const kXMLReaderAttributePrefix = @"@"; 17 | 18 | @interface XMLReader () 19 | 20 | @property (nonatomic, strong) NSMutableArray *dictionaryStack; 21 | @property (nonatomic, strong) NSMutableString *textInProgress; 22 | @property (nonatomic, strong) NSError *errorPointer; 23 | 24 | @end 25 | 26 | 27 | @implementation XMLReader 28 | 29 | #pragma mark - Public methods 30 | 31 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)error 32 | { 33 | XMLReader *reader = [[XMLReader alloc] initWithError:error]; 34 | NSDictionary *rootDictionary = [reader objectWithData:data options:0]; 35 | return rootDictionary; 36 | } 37 | 38 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)error 39 | { 40 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 41 | return [XMLReader dictionaryForXMLData:data error:error]; 42 | } 43 | 44 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)error 45 | { 46 | XMLReader *reader = [[XMLReader alloc] initWithError:error]; 47 | NSDictionary *rootDictionary = [reader objectWithData:data options:options]; 48 | return rootDictionary; 49 | } 50 | 51 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)error 52 | { 53 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 54 | return [XMLReader dictionaryForXMLData:data options:options error:error]; 55 | } 56 | 57 | 58 | #pragma mark - Parsing 59 | 60 | - (id)initWithError:(NSError **)error 61 | { 62 | self = [super init]; 63 | if (self) 64 | { 65 | self.errorPointer = *error; 66 | } 67 | return self; 68 | } 69 | 70 | - (NSDictionary *)objectWithData:(NSData *)data options:(XMLReaderOptions)options 71 | { 72 | // Clear out any old data 73 | self.dictionaryStack = [[NSMutableArray alloc] init]; 74 | self.textInProgress = [[NSMutableString alloc] init]; 75 | 76 | // Initialize the stack with a fresh dictionary 77 | [self.dictionaryStack addObject:[NSMutableDictionary dictionary]]; 78 | 79 | // Parse the XML 80 | NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; 81 | 82 | [parser setShouldProcessNamespaces:(options & XMLReaderOptionsProcessNamespaces)]; 83 | [parser setShouldReportNamespacePrefixes:(options & XMLReaderOptionsReportNamespacePrefixes)]; 84 | [parser setShouldResolveExternalEntities:(options & XMLReaderOptionsResolveExternalEntities)]; 85 | 86 | parser.delegate = self; 87 | BOOL success = [parser parse]; 88 | 89 | // Return the stack's root dictionary on success 90 | if (success) 91 | { 92 | NSDictionary *resultDict = [self.dictionaryStack objectAtIndex:0]; 93 | return resultDict; 94 | } 95 | 96 | return nil; 97 | } 98 | 99 | 100 | #pragma mark - NSXMLParserDelegate methods 101 | 102 | - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict 103 | { 104 | // Get the dictionary for the current level in the stack 105 | NSMutableDictionary *parentDict = [self.dictionaryStack lastObject]; 106 | 107 | // Create the child dictionary for the new element, and initilaize it with the attributes 108 | NSMutableDictionary *childDict = [NSMutableDictionary dictionary]; 109 | [childDict addEntriesFromDictionary:attributeDict]; 110 | 111 | // If there's already an item for this key, it means we need to create an array 112 | id existingValue = [parentDict objectForKey:elementName]; 113 | if (existingValue) 114 | { 115 | NSMutableArray *array = nil; 116 | if ([existingValue isKindOfClass:[NSMutableArray class]]) 117 | { 118 | // The array exists, so use it 119 | array = (NSMutableArray *) existingValue; 120 | } 121 | else 122 | { 123 | // Create an array if it doesn't exist 124 | array = [NSMutableArray array]; 125 | [array addObject:existingValue]; 126 | 127 | // Replace the child dictionary with an array of children dictionaries 128 | [parentDict setObject:array forKey:elementName]; 129 | } 130 | 131 | // Add the new child dictionary to the array 132 | [array addObject:childDict]; 133 | } 134 | else 135 | { 136 | // No existing value, so update the dictionary 137 | [parentDict setObject:childDict forKey:elementName]; 138 | } 139 | 140 | // Update the stack 141 | [self.dictionaryStack addObject:childDict]; 142 | } 143 | 144 | - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 145 | { 146 | // Update the parent dict with text info 147 | NSMutableDictionary *dictInProgress = [self.dictionaryStack lastObject]; 148 | 149 | // Set the text property 150 | if ([self.textInProgress length] > 0) 151 | { 152 | // trim after concatenating 153 | NSString *trimmedString = [self.textInProgress stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 154 | [dictInProgress setObject:[trimmedString mutableCopy] forKey:kXMLReaderTextNodeKey]; 155 | 156 | // Reset the text 157 | self.textInProgress = [[NSMutableString alloc] init]; 158 | } 159 | 160 | // Pop the current dict 161 | [self.dictionaryStack removeLastObject]; 162 | } 163 | 164 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 165 | { 166 | // Build the text value 167 | [self.textInProgress appendString:string]; 168 | } 169 | 170 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError 171 | { 172 | // Set the error pointer to the parser's error object 173 | self.errorPointer = parseError; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /ios/Podfile.template: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '8.0' 3 | use_frameworks! 4 | 5 | pod 'DCTAuth', :git => 'https://github.com/danielctull/DCTAuth.git' 6 | -------------------------------------------------------------------------------- /ios/buildScript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | frameworks="libswiftCore libswiftCoreGraphics libswiftCoreImage libswiftDarwin libswiftDispatch libswiftFoundation libswiftObjectiveC libswiftUIKit libswiftContacts" 4 | 5 | for framework in $frameworks 6 | do 7 | 8 | install_name_tool -change "@rpath/${framework}.dylib" "@loader_path/Frameworks/${framework}.dylib" ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH} 9 | 10 | done 11 | -------------------------------------------------------------------------------- /lib/authProviders.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import {Type, String} from 'valib'; 3 | 4 | const notEmpty = (str) => Type.isString(str) && !String.isEmpty(str) || 'cannot be empty'; 5 | 6 | const isValid = (prop, str, validations=[]) => { 7 | return validations 8 | .map(fn => { 9 | const val = fn(str); 10 | invariant(typeof val === 'boolean', `${prop} ${val}`) 11 | }); 12 | } 13 | const withDefaultValidations = (validations) => Object.assign({}, { 14 | callback_url: [notEmpty] 15 | }, validations); 16 | 17 | const validate = (customValidations={}) => (props) => { 18 | const validations = withDefaultValidations(customValidations); 19 | return Object.keys(props) 20 | .map(property => isValid(property, props[property], validations[property])); 21 | } 22 | 23 | export const authProviders = { 24 | 'twitter': { 25 | auth_version: "1.0", 26 | request_token_url: 'https://api.twitter.com/oauth/request_token', 27 | authorize_url: 'https://api.twitter.com/oauth/authorize', 28 | access_token_url: 'https://api.twitter.com/oauth/access_token', 29 | api_url: 'https://api.twitter.com', 30 | callback_url: ({app_name}) => `${app_name}://oauth-response/twitter`, 31 | 32 | validate: validate({ 33 | consumer_key: [notEmpty], 34 | consumer_secret: [notEmpty] 35 | }) 36 | }, 37 | 'facebook': { 38 | auth_version: "2.0", 39 | authorize_url: 'https://graph.facebook.com/oauth/authorize', 40 | api_url: 'https://graph.facebook.com', 41 | callback_url: ({client_id}) => `fb${client_id}://authorize`, 42 | 43 | validate: validate({ 44 | client_id: [notEmpty], 45 | client_secret: [notEmpty] 46 | }) 47 | }, 48 | 'google': { 49 | auth_version: "2.0", 50 | authorize_url: 'https://accounts.google.com/o/oauth2/auth', 51 | access_token_url: 'https://accounts.google.com/o/oauth2/token', 52 | callback_url: ({app_name}) => `${app_name}://oauth-response`, 53 | validate: validate() 54 | }, 55 | 'github': { 56 | auth_version: '2.0', 57 | authorize_url: 'https://github.com/login/oauth/authorize', 58 | access_token_url: 'https://github.com/login/oauth/access_token', 59 | api_url: 'https://api.github.com', 60 | callback_url: ({app_name}) => `${app_name}://oauth`, 61 | validate: validate() 62 | }, 63 | 'slack': { 64 | auth_version: '2.0', 65 | authorize_url: 'https://slack.com/oauth/authorize', 66 | access_token_url: 'https://slack.com/api/oauth.access', 67 | api_url: 'https://slack.com/api', 68 | callback_url: ({app_name}) => `${app_name}://oauth`, 69 | defaultParams: { 70 | token: 'access_token' 71 | }, 72 | validate: validate({ 73 | client_id: [notEmpty], 74 | client_secret: [notEmpty] 75 | }) 76 | }, 77 | 'spotify': { 78 | auth_version: "2.0", 79 | authorize_url: 'https://accounts.spotify.com/authorize', 80 | api_url: 'https://api.spotify.com/', 81 | callback_url: ({app_name}) => `${app_name}://authorize`, 82 | 83 | validate: validate({ 84 | client_id: [notEmpty], 85 | client_secret: [notEmpty] 86 | }) 87 | }, 88 | } 89 | 90 | export default authProviders; 91 | -------------------------------------------------------------------------------- /lib/promisify.js: -------------------------------------------------------------------------------- 1 | import {NativeModules, NativeAppEventEmitter} from 'react-native'; 2 | const OAuthManagerBridge = NativeModules.OAuthManager; 3 | 4 | export const promisify = (fn, NativeModule) => (...args) => { 5 | const Module = NativeModule ? NativeModule : OAuthManagerBridge; 6 | return new Promise((resolve, reject) => { 7 | const handler = (err, resp) => { 8 | err ? reject(err) : resolve(resp); 9 | } 10 | args.push(handler); 11 | (typeof fn === 'function' ? fn : Module[fn]) 12 | .call(Module, ...args); 13 | }); 14 | }; 15 | 16 | export default promisify -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-oauth", 3 | "version": "2.1.18", 4 | "author": "Ari Lerner (https://fullstackreact.com)", 5 | "description": "An oauth manager for dealing with the complexities of oauth", 6 | "main": "./react-native-oauth.js", 7 | "scripts": { 8 | "start": "node node_modules/react-native/local-cli/cli.js start", 9 | "build": "./node_modules/.bin/babel --ignore 'node_modules,dist' --source-maps=true --out-dir=dist .", 10 | "dev": "npm run compile -- --watch", 11 | "lint": "eslint ./src", 12 | "publish_pages": "gh-pages -d public/", 13 | "test": "./node_modules/.bin/mocha" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/fullstackreact/react-native-oauth.git" 18 | }, 19 | "license": "ISC", 20 | "keywords": [ 21 | "react", 22 | "react-native", 23 | "react-native-firestack", 24 | "firestack", 25 | "firebase", 26 | "oauth", 27 | "twitter" 28 | ], 29 | "peerDependencies": { 30 | "react": "*", 31 | "react-native": "*" 32 | }, 33 | "rnpm": { 34 | "commands": { 35 | "prelink": "node_modules/react-native-oauth/bin/prepare.sh", 36 | "postlink": "node_modules/react-native-oauth/bin/cocoapods.sh" 37 | }, 38 | "ios": { 39 | "project": "ios/OAuthManager.xcodeproj" 40 | }, 41 | "android": { 42 | "packageInstance": "new OAuthManagerPackage()" 43 | } 44 | }, 45 | "dependencies": { 46 | "invariant": "^2.2.1", 47 | "valib": "^2.0.0" 48 | }, 49 | "devDependencies": { 50 | "babel-plugin-transform-async-to-generator": "^6.24.1", 51 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 52 | "babel-plugin-transform-runtime": "^6.23.0", 53 | "babel-preset-env": "^1.7.0", 54 | "babel-runtime": "^6.26.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /react-native-oauth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule OAuthManager 3 | * @flow 4 | */ 5 | import { 6 | NativeModules, 7 | AsyncStorage 8 | } from 'react-native'; 9 | import invariant from 'invariant'; 10 | 11 | const OAuthManagerBridge = NativeModules.OAuthManager; 12 | 13 | let configured = false; 14 | const STORAGE_KEY = 'ReactNativeOAuth'; 15 | import promisify from './lib/promisify' 16 | import defaultProviders from './lib/authProviders'; 17 | 18 | let authProviders = defaultProviders; 19 | 20 | const identity = (props) => props; 21 | /** 22 | * Manager is the OAuth layer 23 | **/ 24 | export default class OAuthManager { 25 | constructor(appName, opts={}) { 26 | invariant(appName && appName != '', `You must provide an appName to the OAuthManager`); 27 | 28 | this.appName = appName; 29 | this._options = opts; 30 | } 31 | 32 | addProvider(provider) { 33 | authProviders = Object.assign({}, authProviders, provider); 34 | } 35 | 36 | configure(providerConfigs) { 37 | return this.configureProviders(providerConfigs) 38 | } 39 | 40 | authorize(provider, opts={}) { 41 | const options = Object.assign({}, this._options, opts, { 42 | app_name: this.appName 43 | }) 44 | return promisify('authorize')(provider, options); 45 | } 46 | 47 | savedAccounts(opts={}) { 48 | // const options = Object.assign({}, this._options, opts, { 49 | // app_name: this.appName 50 | // }) 51 | // return promisify('getSavedAccounts')(options); 52 | const promises = this.providers() 53 | .map(name => { 54 | return this.savedAccount(name) 55 | .catch(err => ({provider: name, status: "error"})); 56 | }); 57 | return Promise.all(promises) 58 | .then((accountResp) => { 59 | const accounts = accountResp.filter(acc => acc.status == "ok"); 60 | return { accounts } 61 | }); 62 | } 63 | 64 | savedAccount(provider) { 65 | const options = Object.assign({}, this._options, { 66 | app_name: this.appName 67 | }) 68 | return promisify('getSavedAccount')(provider, options); 69 | } 70 | 71 | makeRequest(provider, url, opts={}) { 72 | const options = Object.assign({}, this._options, opts, { 73 | app_name: this.appName 74 | }); 75 | 76 | console.log('making request', provider, url, opts); 77 | 78 | return promisify('makeRequest')(provider, url, options) 79 | .then(response => { 80 | // Little bit of a hack to support Android until we have a better 81 | // way of decoding the JSON response on the Android side 82 | if (response && response.data && typeof response.data === "string") { 83 | response.data = JSON.parse(response.data); 84 | } 85 | return response; 86 | }); 87 | } 88 | 89 | deauthorize(provider) { 90 | return promisify('deauthorize')(provider); 91 | } 92 | 93 | providers() { 94 | return OAuthManager.providers(); 95 | } 96 | 97 | static providers() { 98 | return Object.keys(authProviders); 99 | } 100 | 101 | static isSupported(name) { 102 | return OAuthManager.providers().indexOf(name) >= 0; 103 | } 104 | 105 | // Private 106 | /** 107 | * Configure a single provider 108 | * 109 | * 110 | * @param {string} name of the provider 111 | * @param {object} additional configuration 112 | * 113 | **/ 114 | configureProvider(name, props) { 115 | invariant(OAuthManager.isSupported(name), `The provider ${name} is not supported yet`); 116 | 117 | const providerCfg = Object.assign({}, authProviders[name]); 118 | let { validate = identity, transform = identity, callback_url } = providerCfg; 119 | delete providerCfg.transform; 120 | delete providerCfg.validate; 121 | 122 | let config = Object.assign({}, { 123 | app_name: this.appName, 124 | callback_url 125 | }, providerCfg, props); 126 | 127 | if (config.defaultParams) { 128 | delete config.defaultParams; 129 | } 130 | 131 | config = Object.keys(config) 132 | .reduce((sum, key) => ({ 133 | ...sum, 134 | [key]: typeof config[key] === 'function' ? config[key](config) : config[key] 135 | }), {}); 136 | 137 | validate(config); 138 | 139 | return promisify('configureProvider')(name, config); 140 | } 141 | 142 | configureProviders(providerConfigs) { 143 | providerConfigs = providerConfigs || this._options; 144 | const promises = Object 145 | .keys(providerConfigs) 146 | .map(name => 147 | this.configureProvider(name, providerConfigs[name])); 148 | return Promise.all(promises) 149 | .then(() => this); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /resources/capabilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/capabilities.png -------------------------------------------------------------------------------- /resources/facebook/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/facebook/app.png -------------------------------------------------------------------------------- /resources/facebook/dev.facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/facebook/dev.facebook.png -------------------------------------------------------------------------------- /resources/facebook/facebook-redirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/facebook/facebook-redirect.png -------------------------------------------------------------------------------- /resources/facebook/redirect-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/facebook/redirect-url.png -------------------------------------------------------------------------------- /resources/facebook/url-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/facebook/url-scheme.png -------------------------------------------------------------------------------- /resources/github/apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/github/apps.png -------------------------------------------------------------------------------- /resources/google/android-creds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/google/android-creds.png -------------------------------------------------------------------------------- /resources/google/auth-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/google/auth-page.png -------------------------------------------------------------------------------- /resources/google/creds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/google/creds.png -------------------------------------------------------------------------------- /resources/google/url-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/google/url-scheme.png -------------------------------------------------------------------------------- /resources/header-search-paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/header-search-paths.png -------------------------------------------------------------------------------- /resources/info-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/info-panel.png -------------------------------------------------------------------------------- /resources/slack/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/slack/create.png -------------------------------------------------------------------------------- /resources/slack/creds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/slack/creds.png -------------------------------------------------------------------------------- /resources/slack/dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/slack/dev.png -------------------------------------------------------------------------------- /resources/slack/getting_started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/slack/getting_started.png -------------------------------------------------------------------------------- /resources/slack/redirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/slack/redirect.png -------------------------------------------------------------------------------- /resources/twitter/api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/twitter/api-key.png -------------------------------------------------------------------------------- /resources/twitter/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/twitter/app.png -------------------------------------------------------------------------------- /resources/twitter/callback-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/twitter/callback-url.png -------------------------------------------------------------------------------- /resources/twitter/url-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/twitter/url-scheme.png -------------------------------------------------------------------------------- /resources/url-schemes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullstackreact/react-native-oauth/afaffc988abbc39044850f1a1a95979d19bfb950/resources/url-schemes.png --------------------------------------------------------------------------------