├── README.md ├── client └── MobileApp │ ├── .babelrc │ ├── .buckconfig │ ├── .eslintrc │ ├── .flowconfig │ ├── .gitignore │ ├── .watchmanconfig │ ├── __tests__ │ ├── index.android.js │ └── index.ios.js │ ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Entypo.ttf │ │ │ │ ├── EvilIcons.ttf │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── Foundation.ttf │ │ │ │ ├── Ionicons.ttf │ │ │ │ ├── MaterialIcons.ttf │ │ │ │ ├── Octicons.ttf │ │ │ │ └── Zocial.ttf │ │ │ ├── java │ │ │ └── com │ │ │ │ └── mobileapp │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle │ ├── index.android.js │ ├── index.ios.js │ ├── ios │ ├── MobileApp.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── MobileApp.xcscheme │ ├── MobileApp │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── MobileAppTests │ │ ├── Info.plist │ │ └── MobileAppTests.m │ ├── package.json │ ├── src │ ├── App.js │ ├── components │ │ └── FormMessage │ │ │ └── index.js │ ├── data │ │ ├── reducer.js │ │ └── users │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── api.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ ├── scenes │ │ ├── Main │ │ │ ├── index.js │ │ │ └── scenes │ │ │ │ ├── Login │ │ │ │ └── index.js │ │ │ │ ├── Register │ │ │ │ └── index.js │ │ │ │ └── Users │ │ │ │ └── index.js │ │ └── Splash │ │ │ └── index.js │ ├── services │ │ ├── api │ │ │ ├── config.js │ │ │ └── index.js │ │ ├── persist │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── reducer.js │ │ ├── routeHistory │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ └── session │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── api.js │ │ │ ├── index.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ └── store.js │ └── yarn.lock └── server ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .sailsrc ├── .tmp └── localDiskDb.db ├── api ├── controllers │ ├── ClientsController.js │ ├── UsersAuthController.js │ └── UsersController.js ├── models │ ├── Clients.js │ ├── Tokens.js │ └── Users.js ├── policies │ ├── hasClientId.js │ ├── hasOAuthBearer.js │ └── hasRefreshToken.js ├── responses │ ├── badRequest.js │ ├── created.js │ ├── forbidden.js │ ├── notFound.js │ ├── ok.js │ ├── serverError.js │ └── unauthorized.js └── services │ ├── .gitkeep │ └── PasswordService.js ├── app.js ├── config ├── blueprints.js ├── bootstrap.js ├── connections.js ├── cors.js ├── csrf.js ├── env │ ├── development.js │ └── production.js ├── globals.js ├── http.js ├── i18n.js ├── locales │ ├── _README.md │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── log.js ├── models.js ├── oauth.js ├── passport.js ├── policies.js ├── routes.js ├── session.js ├── sockets.js └── views.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # react-native-authentication 2 | 3 | The main goal of this project is to show you how to register and authenticate a user and access protected resources from a React-Native app to a NodeJS server. 4 | 5 | If you want to know more about this project, you can read this article which describe how it works: 6 | 7 | The Essential Boilerplate to Authenticate Users on your React-Native app.
8 | https://medium.com/@alexmngn/the-essential-boilerplate-to-authenticate-users-on-your-react-native-app-f7a8e0e04a42 9 | 10 | This project has been tested with Node v6.0.0 and NPM 3.8.6. 11 | 12 | ## Client 13 | 14 | ### Installation 15 | 16 | If you don't have React-Native installed on your computer, run the following: 17 | ``` 18 | npm install -g react-native-cli 19 | ``` 20 | 21 | Go in the `client/MobileApp` directory, and run the following: 22 | 23 | ``` 24 | npm install 25 | ``` 26 | 27 | ### Run 28 | 29 | iOS: 30 | ``` 31 | react-native run-ios 32 | ``` 33 | Android: 34 | 35 | You will need to follow a few steps to run the client: 36 | 37 | - Open the file `client/MobileApp/src/services/api/config.js` 38 | - Modify `localhost` with the IP address of your machine (usually something like 192.168.0.10) 39 | ``` 40 | export default { 41 | clientId: '8puWuJWZYls1Ylawxm6CMiYREhsGGSyw', 42 | url: 'http://192.168.0.10:1337', 43 | }; 44 | ``` 45 | - Create a file called `local.properties` in the `/MobileApp/android` folder and add the following line (replace the target with the path to your SDK): `sdk.dir = /Users/Alexis/Library/Android/sdk` 46 | - Open an Emulator (from Android Studio) or plug an Android device on your computer. 47 | - Then you can run the following in terminal: 48 | ``` 49 | react-native run-android 50 | ``` 51 | 52 | ### Use 53 | 54 | You can login with the following user: 55 | - Email: **user1@facebook.com** 56 | - Password: **12345678** 57 | 58 | There is also a Client-ID that has already been generated, currently hard-coded in the client api config: 59 | - **8puWuJWZYls1Ylawxm6CMiYREhsGGSyw** 60 | 61 | 62 | ## Server 63 | 64 | ### Installation 65 | 66 | 67 | If you don't have SailsJS installed on your computer, run the following: 68 | ``` 69 | npm install -g sails 70 | ``` 71 | 72 | Go in the `server` directory, then run the following: 73 | 74 | ``` 75 | npm install 76 | ``` 77 | 78 | ### Run 79 | 80 | Run the following in the terminal: 81 | 82 | ``` 83 | sails lift 84 | ``` 85 | 86 | This will create a server listening on port 3000, you can access it from http://localhost:3000/. The server needs to run at all time when you use the client. 87 | 88 | ### Entry-points: 89 | 90 | An open entry-point is provided to generate this ID. This should not be done in production: 91 | 92 | - `POST /clients` 93 | 94 | The non-protected entry-points allow authentication and registration: 95 | 96 | - `POST /users`: Create a new user 97 | - `POST /users/auth`: Authenticate and retrieve the access and refresh tokens in exchange of email/password 98 | - `POST /users/auth/refresh`: Authenticate and retrieve the access token in exchange of the refresh token. 99 | 100 | The protected entry-point allows everything else: 101 | - `GET /users`: Retrieve the list of users 102 | - `POST /users/auth/revoke`: Log out, revoke access by destroying the user tokens 103 | -------------------------------------------------------------------------------- /client/MobileApp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } -------------------------------------------------------------------------------- /client/MobileApp/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /client/MobileApp/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": false, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "plugins": [ 9 | "react", 10 | "react-native" 11 | ], 12 | "extends": "airbnb", 13 | 14 | "rules": { 15 | "indent": [2, "tab", {"SwitchCase": 1}], 16 | "no-else-return": 0, 17 | "no-tabs": 0, 18 | "no-console": 0, 19 | "consistent-return": 0, 20 | "import/no-extraneous-dependencies": 0, 21 | "import/no-unresolved": [2, { ignore: ['MobileApp'] }], 22 | "import/extensions": [2, { "js": "never" }], 23 | "import/prefer-default-export": 0, 24 | "react/jsx-indent": [2, 'tab'], 25 | "react/jsx-indent-props": [2, 'tab'], 26 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/MobileApp/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*[.]android.js 5 | 6 | # Ignore templates with `@flow` in header 7 | .*/local-cli/generator.* 8 | 9 | # Ignore malformed json 10 | .*/node_modules/y18n/test/.*\.json 11 | 12 | # Ignore the website subdir 13 | /website/.* 14 | 15 | # Ignore BUCK generated dirs 16 | /\.buckd/ 17 | 18 | # Ignore unexpected extra @providesModule 19 | .*/node_modules/commoner/test/source/widget/share.js 20 | 21 | # Ignore duplicate module providers 22 | # For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root 23 | .*/Libraries/react-native/React.js 24 | .*/Libraries/react-native/ReactNative.js 25 | .*/node_modules/jest-runtime/build/__tests__/.* 26 | 27 | [include] 28 | 29 | [libs] 30 | node_modules/react-native/Libraries/react-native/react-native-interface.js 31 | node_modules/react-native/flow 32 | flow/ 33 | 34 | [options] 35 | module.system=haste 36 | 37 | esproposal.class_static_fields=enable 38 | esproposal.class_instance_fields=enable 39 | 40 | experimental.strict_type_args=true 41 | 42 | munge_underscores=true 43 | 44 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 45 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 46 | 47 | suppress_type=$FlowIssue 48 | suppress_type=$FlowFixMe 49 | suppress_type=$FixMe 50 | 51 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 52 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-3]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 53 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 54 | 55 | unsafe.enable_getters_and_setters=true 56 | 57 | [version] 58 | ^0.33.0 59 | -------------------------------------------------------------------------------- /client/MobileApp/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | -------------------------------------------------------------------------------- /client/MobileApp/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /client/MobileApp/__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /client/MobileApp/__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.mobileapp', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.mobileapp', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"] 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 23 87 | buildToolsVersion "23.0.1" 88 | 89 | defaultConfig { 90 | applicationId "com.mobileapp" 91 | minSdkVersion 16 92 | targetSdkVersion 22 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | } 127 | 128 | dependencies { 129 | compile project(':react-native-vector-icons') 130 | compile fileTree(dir: "libs", include: ["*.jar"]) 131 | compile "com.android.support:appcompat-v7:23.0.1" 132 | compile "com.facebook.react:react-native:+" // From node_modules 133 | } 134 | 135 | // Run this once to be able to run the application with BUCK 136 | // puts all compile dependencies into folder libs for BUCK to use 137 | task copyDownloadableDepsToLibs(type: Copy) { 138 | from configurations.compile 139 | into 'libs' 140 | } 141 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/java/com/mobileapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mobileapp; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "MobileApp"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/java/com/mobileapp/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.mobileapp; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.oblador.vectoricons.VectorIconsPackage; 8 | import com.facebook.react.ReactInstanceManager; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public class MainApplication extends Application implements ReactApplication { 17 | 18 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 19 | @Override 20 | protected boolean getUseDeveloperSupport() { 21 | return BuildConfig.DEBUG; 22 | } 23 | 24 | @Override 25 | protected List getPackages() { 26 | return Arrays.asList( 27 | new MainReactPackage(), 28 | new VectorIconsPackage() 29 | ); 30 | } 31 | }; 32 | 33 | @Override 34 | public ReactNativeHost getReactNativeHost() { 35 | return mReactNativeHost; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MobileApp 3 | 4 | -------------------------------------------------------------------------------- /client/MobileApp/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/MobileApp/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/MobileApp/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /client/MobileApp/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/client/MobileApp/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client/MobileApp/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 6 | -------------------------------------------------------------------------------- /client/MobileApp/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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /client/MobileApp/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 | -------------------------------------------------------------------------------- /client/MobileApp/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /client/MobileApp/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /client/MobileApp/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'MobileApp' 2 | 3 | include ':app' 4 | include ':react-native-vector-icons' 5 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 6 | -------------------------------------------------------------------------------- /client/MobileApp/index.android.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppRegistry, 3 | } from 'react-native'; 4 | import App from 'MobileApp/src/App'; 5 | 6 | AppRegistry.registerComponent('MobileApp', () => App); 7 | -------------------------------------------------------------------------------- /client/MobileApp/index.ios.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppRegistry, 3 | } from 'react-native'; 4 | import App from 'MobileApp/src/App'; 5 | 6 | AppRegistry.registerComponent('MobileApp', () => App); 7 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp.xcodeproj/xcshareddata/xcschemes/MobileApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import "RCTBundleURLProvider.h" 13 | #import "RCTRootView.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 22 | 23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 24 | moduleName:@"MobileApp" 25 | initialProperties:nil 26 | launchOptions:launchOptions]; 27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 28 | 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | UIViewController *rootViewController = [UIViewController new]; 31 | rootViewController.view = rootView; 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | NSExceptionDomains 44 | 45 | localhost 46 | 47 | NSExceptionAllowsInsecureHTTPLoads 48 | 49 | 50 | 51 | 52 | UIAppFonts 53 | 54 | Entypo.ttf 55 | EvilIcons.ttf 56 | FontAwesome.ttf 57 | Foundation.ttf 58 | Ionicons.ttf 59 | MaterialIcons.ttf 60 | Octicons.ttf 61 | Zocial.ttf 62 | 63 | 64 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileApp/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /client/MobileApp/ios/MobileAppTests/MobileAppTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface MobileAppTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation MobileAppTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /client/MobileApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MobileApp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "buffer": "5.0.0", 11 | "fetchival": "0.3.2", 12 | "native-base": "0.5.13", 13 | "qs": "6.3.0", 14 | "react": "15.3.2", 15 | "react-native": "0.36.1", 16 | "react-redux": "4.4.5", 17 | "redux": "3.6.0", 18 | "redux-persist": "3.4.0", 19 | "redux-persist-transform-filter": "0.0.4", 20 | "redux-thunk": "2.1.0", 21 | "remote-redux-devtools": "0.5.2" 22 | }, 23 | "jest": { 24 | "preset": "jest-react-native" 25 | }, 26 | "devDependencies": { 27 | "babel-eslint": "7.1.0", 28 | "babel-jest": "16.0.0", 29 | "babel-preset-react-native": "1.9.0", 30 | "eslint": "3.9.1", 31 | "eslint-config-airbnb": "12.0.0", 32 | "eslint-plugin-import": "1.16.0", 33 | "eslint-plugin-jsx-a11y": "2.2.3", 34 | "eslint-plugin-react": "6.6.0", 35 | "eslint-plugin-react-native": "2.0.0", 36 | "jest": "16.0.2", 37 | "jest-react-native": "16.1.0", 38 | "react-test-renderer": "15.3.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/MobileApp/src/App.js: -------------------------------------------------------------------------------- 1 | /* global XMLHttpRequest */ 2 | 3 | import React, { Component } from 'react'; 4 | import { 5 | StyleSheet, 6 | Navigator, 7 | View, 8 | } from 'react-native'; 9 | import { Provider } from 'react-redux'; 10 | 11 | import store from 'MobileApp/src/store'; 12 | import * as session from 'MobileApp/src/services/session'; 13 | import * as routeHistoryActions from 'MobileApp/src/services/routeHistory/actions'; 14 | import Splash from 'MobileApp/src/scenes/Splash'; 15 | import Main from 'MobileApp/src/scenes/Main'; 16 | import Login from 'MobileApp/src/scenes/Main/scenes/Login'; 17 | import Register from 'MobileApp/src/scenes/Main/scenes/Register'; 18 | import Users from 'MobileApp/src/scenes/Main/scenes/Users'; 19 | 20 | // This is used in order to see requests on the Chrome DevTools 21 | XMLHttpRequest = GLOBAL.originalXMLHttpRequest ? 22 | GLOBAL.originalXMLHttpRequest : 23 | GLOBAL.XMLHttpRequest; 24 | 25 | const transition = Navigator.SceneConfigs.HorizontalSwipeJump; 26 | transition.gestures = null; 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: '#eee', 32 | }, 33 | }); 34 | 35 | const routeStack = [ 36 | { name: 'Main', component: Main }, 37 | { name: 'Login', component: Login }, 38 | { name: 'Register', component: Register }, 39 | { name: 'Users', component: Users }, 40 | ]; 41 | 42 | class App extends Component { 43 | constructor(props) { 44 | super(props); 45 | 46 | this.state = { 47 | initialRoute: null, 48 | }; 49 | } 50 | 51 | componentDidMount() { 52 | // Waits for the redux store to be populated with the previously saved state, 53 | // then it will try to auto-login the user. 54 | const unsubscribe = store.subscribe(() => { 55 | if (store.getState().services.persist.isHydrated) { 56 | unsubscribe(); 57 | this.autoLogin(); 58 | } 59 | }); 60 | } 61 | 62 | autoLogin() { 63 | session.refreshToken().then(() => { 64 | this.setState({ initialRoute: routeStack[3] }); 65 | }).catch(() => { 66 | this.setState({ initialRoute: routeStack[0] }); 67 | }); 68 | } 69 | 70 | renderContent() { 71 | if (!this.state.initialRoute) { 72 | return ; 73 | } 74 | 75 | return ( 76 | Navigator.SceneConfigs.HorizontalSwipeJump} 80 | onWillFocus={route => store.dispatch(routeHistoryActions.push(route))} 81 | renderScene={(route, navigator) => 82 | 83 | } 84 | /> 85 | ); 86 | } 87 | 88 | render() { 89 | return ( 90 | 91 | 92 | {this.renderContent()} 93 | 94 | 95 | ); 96 | } 97 | } 98 | 99 | export default App; 100 | -------------------------------------------------------------------------------- /client/MobileApp/src/components/FormMessage/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | StyleSheet, 4 | } from 'react-native'; 5 | import { 6 | Icon, 7 | Text, 8 | View, 9 | } from 'native-base'; 10 | 11 | const styles = StyleSheet.create({ 12 | errorContainer: { 13 | flexDirection: 'row', 14 | alignItems: 'center', 15 | marginBottom: 20, 16 | }, 17 | error: { 18 | marginRight: 10, 19 | }, 20 | }); 21 | 22 | const colors = { error: 'red', warning: 'yellow' }; 23 | 24 | const FormMessage = (props) => { 25 | const style = { color: colors[props.type] || colors.error }; 26 | if (props.message) { 27 | return ( 28 | 29 | 30 | 31 | {props.message} 32 | 33 | 34 | ); 35 | } 36 | }; 37 | 38 | FormMessage.propTypes = { 39 | message: PropTypes.string, 40 | type: PropTypes.string, 41 | }; 42 | 43 | FormMessage.defaultProps = { 44 | type: 'error', 45 | }; 46 | 47 | export default FormMessage; 48 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as usersReducer } from './users/reducer'; 3 | 4 | export const reducer = combineReducers({ 5 | users: usersReducer, 6 | }); 7 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/users/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const UPDATE = 'users/UPDATE'; 2 | export const EMPTY = 'users/EMPTY'; 3 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/users/actions.js: -------------------------------------------------------------------------------- 1 | import * as api from './api'; 2 | import * as actionTypes from './actionTypes'; 3 | 4 | const update = items => ({ 5 | type: actionTypes.UPDATE, 6 | items, 7 | }); 8 | 9 | export const empty = () => ({ 10 | type: actionTypes.EMPTY, 11 | }); 12 | 13 | export const get = payload => 14 | dispatch => 15 | api.get(payload) 16 | .then(response => dispatch(update(response.users))); 17 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/users/api.js: -------------------------------------------------------------------------------- 1 | import { fetchApi } from 'MobileApp/src/services/api'; 2 | 3 | const endPoints = { 4 | create: '/users', 5 | get: '/users', 6 | }; 7 | 8 | export const create = payload => fetchApi(endPoints.create, payload, 'post'); 9 | 10 | export const get = payload => fetchApi(endPoints.get, payload, 'get'); 11 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/users/reducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | const initialState = { 4 | items: {}, 5 | }; 6 | 7 | export const reducer = (state = initialState, action) => { 8 | switch (action.type) { 9 | case actionTypes.UPDATE: 10 | return { 11 | items: { 12 | ...state.items, 13 | ...action.items.reduce((prev, curr) => ({ 14 | ...prev, 15 | [curr.id]: curr, 16 | }), {}), 17 | }, 18 | }; 19 | case actionTypes.EMPTY: 20 | return { 21 | items: {}, 22 | }; 23 | default: 24 | return state; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /client/MobileApp/src/data/users/selectors.js: -------------------------------------------------------------------------------- 1 | import store from 'MobileApp/src/store'; 2 | 3 | export const getAll = () => { 4 | const { items } = store.getState().data.users; 5 | const itemsArray = Object.keys(items).map(itemKey => items[itemKey]); 6 | itemsArray.sort((item1, item2) => item1.id > item2.id); 7 | return itemsArray; 8 | }; 9 | -------------------------------------------------------------------------------- /client/MobileApp/src/scenes/Main/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | StyleSheet, 4 | } from 'react-native'; 5 | import { 6 | Container, 7 | Header, 8 | Title, 9 | Button, 10 | View, 11 | } from 'native-base'; 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | flex: 1, 16 | }, 17 | button: { 18 | marginTop: 20, 19 | alignSelf: 'center', 20 | width: 150, 21 | }, 22 | }); 23 | 24 | const Main = (props) => { 25 | const routeStack = props.navigator.getCurrentRoutes(); 26 | return ( 27 | 28 | 29 |
30 | Welcome 31 |
32 | 33 | 40 | 47 | 48 |
49 |
50 | ); 51 | }; 52 | 53 | Main.propTypes = { 54 | navigator: PropTypes.shape({ 55 | getCurrentRoutes: PropTypes.func, 56 | jumpTo: PropTypes.func, 57 | }), 58 | }; 59 | 60 | export default Main; 61 | -------------------------------------------------------------------------------- /client/MobileApp/src/scenes/Main/scenes/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard'; 3 | import { 4 | TouchableWithoutFeedback, 5 | StyleSheet, 6 | } from 'react-native'; 7 | import { 8 | Container, 9 | Header, 10 | Title, 11 | InputGroup, 12 | Input, 13 | Button, 14 | Icon, 15 | Text, 16 | View, 17 | Spinner, 18 | } from 'native-base'; 19 | 20 | import FormMessage from 'MobileApp/src/components/FormMessage'; 21 | import * as session from 'MobileApp/src/services/session'; 22 | import * as api from 'MobileApp/src/services/api'; 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | position: 'absolute', 27 | top: 0, 28 | bottom: 0, 29 | left: 0, 30 | right: 0, 31 | }, 32 | content: { 33 | padding: 30, 34 | flex: 1, 35 | }, 36 | shadow: { 37 | flex: 1, 38 | width: null, 39 | height: null, 40 | }, 41 | inputIcon: { 42 | width: 30, 43 | }, 44 | input: { 45 | marginBottom: 20, 46 | }, 47 | button: { 48 | marginTop: 20, 49 | alignSelf: 'center', 50 | width: 150, 51 | }, 52 | error: { 53 | color: 'red', 54 | marginBottom: 20, 55 | }, 56 | }); 57 | 58 | class Login extends Component { 59 | static propTypes = { 60 | navigator: PropTypes.shape({ 61 | getCurrentRoutes: PropTypes.func, 62 | jumpTo: PropTypes.func, 63 | }), 64 | } 65 | 66 | constructor(props) { 67 | super(props); 68 | 69 | this.initialState = { 70 | isLoading: false, 71 | error: null, 72 | email: 'user1@facebook.com', 73 | password: '12345678', 74 | }; 75 | this.state = this.initialState; 76 | } 77 | 78 | onPressLogin() { 79 | this.setState({ 80 | isLoading: true, 81 | error: '', 82 | }); 83 | dismissKeyboard(); 84 | 85 | session.authenticate(this.state.email, this.state.password) 86 | .then(() => { 87 | this.setState(this.initialState); 88 | const routeStack = this.props.navigator.getCurrentRoutes(); 89 | this.props.navigator.jumpTo(routeStack[3]); 90 | }) 91 | .catch((exception) => { 92 | // Displays only the first error message 93 | const error = api.exceptionExtractError(exception); 94 | this.setState({ 95 | isLoading: false, 96 | ...(error ? { error } : {}), 97 | }); 98 | 99 | if (!error) { 100 | throw exception; 101 | } 102 | }); 103 | } 104 | 105 | onPressBack() { 106 | const routeStack = this.props.navigator.getCurrentRoutes(); 107 | this.props.navigator.jumpTo(routeStack[0]); 108 | } 109 | 110 | renderError() { 111 | if (this.state.error) { 112 | return ( 113 | 116 | {this.state.error} 117 | 118 | ); 119 | } 120 | } 121 | 122 | render() { 123 | return ( 124 | 125 | 126 |
127 | 133 | Login 134 |
135 | 138 | 141 | {this.state.error ? ( 142 | 143 | ) : null} 144 | 145 | 146 | this.setState({ email })} 152 | value={this.state.email} 153 | /> 154 | 155 | 156 | 157 | this.setState({ password })} 160 | value={this.state.password} 161 | secureTextEntry 162 | /> 163 | 164 | {this.state.isLoading ? ( 165 | 166 | ) : ( 167 | 173 | )} 174 | 175 | 176 |
177 |
178 | ); 179 | } 180 | } 181 | 182 | export default Login; 183 | -------------------------------------------------------------------------------- /client/MobileApp/src/scenes/Main/scenes/Register/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard'; 3 | import { 4 | TouchableWithoutFeedback, 5 | StyleSheet, 6 | } from 'react-native'; 7 | import { 8 | Container, 9 | Header, 10 | Title, 11 | InputGroup, 12 | Input, 13 | Button, 14 | Spinner, 15 | Icon, 16 | View, 17 | } from 'native-base'; 18 | 19 | import * as usersApi from 'MobileApp/src/data/users/api'; 20 | import * as session from 'MobileApp/src/services/session'; 21 | import * as api from 'MobileApp/src/services/api'; 22 | import FormMessage from 'MobileApp/src/components/FormMessage'; 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | position: 'absolute', 27 | top: 0, 28 | bottom: 0, 29 | left: 0, 30 | right: 0, 31 | }, 32 | content: { 33 | padding: 30, 34 | flex: 1, 35 | }, 36 | shadow: { 37 | flex: 1, 38 | width: null, 39 | height: null, 40 | }, 41 | input: { 42 | marginBottom: 20, 43 | }, 44 | inputIcon: { 45 | width: 30, 46 | }, 47 | button: { 48 | marginTop: 20, 49 | alignSelf: 'center', 50 | width: 150, 51 | }, 52 | }); 53 | 54 | class Register extends Component { 55 | static propTypes = { 56 | navigator: PropTypes.shape({ 57 | getCurrentRoutes: PropTypes.func, 58 | jumpTo: PropTypes.func, 59 | }), 60 | } 61 | 62 | constructor(props) { 63 | super(props); 64 | 65 | this.initialState = { 66 | isLoading: false, 67 | error: null, 68 | firstName: '', 69 | email: '', 70 | password: '', 71 | }; 72 | this.state = this.initialState; 73 | } 74 | 75 | onPressRegister() { 76 | this.setState({ 77 | isLoading: true, 78 | error: '', 79 | }); 80 | dismissKeyboard(); 81 | 82 | const { firstName, email, password } = this.state; 83 | usersApi.create({ firstName, email, password }) 84 | .then(() => { 85 | session.authenticate(email, password) 86 | .then(() => { 87 | this.setState(this.initialState); 88 | const routeStack = this.props.navigator.getCurrentRoutes(); 89 | this.props.navigator.jumpTo(routeStack[3]); 90 | }); 91 | }) 92 | .catch((exception) => { 93 | // Displays only the first error message 94 | const error = api.exceptionExtractError(exception); 95 | const newState = { 96 | isLoading: false, 97 | ...(error ? { error } : {}), 98 | }; 99 | this.setState(newState); 100 | }); 101 | } 102 | 103 | onPressBack() { 104 | const routeStack = this.props.navigator.getCurrentRoutes(); 105 | this.props.navigator.jumpTo(routeStack[0]); 106 | } 107 | 108 | render() { 109 | return ( 110 | 111 | 112 |
113 | 119 | Register 120 |
121 | 124 | 127 | {this.state.error ? ( 128 | 129 | ) : null} 130 | 131 | 132 | this.setState({ firstName })} 136 | value={this.state.firstName} 137 | /> 138 | 139 | 140 | 141 | this.setState({ email })} 147 | value={this.state.email} 148 | /> 149 | 150 | 151 | 152 | this.setState({ password })} 155 | value={this.state.password} 156 | secureTextEntry 157 | /> 158 | 159 | {this.state.isLoading ? ( 160 | 161 | ) : ( 162 | 168 | )} 169 | 170 | 171 |
172 |
173 | ); 174 | } 175 | } 176 | 177 | export default Register; 178 | -------------------------------------------------------------------------------- /client/MobileApp/src/scenes/Main/scenes/Users/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { 3 | StyleSheet, 4 | } from 'react-native'; 5 | import { 6 | Container, 7 | Header, 8 | Title, 9 | Content, 10 | Icon, 11 | Button, 12 | List, 13 | ListItem, 14 | Text, 15 | Spinner, 16 | View, 17 | } from 'native-base'; 18 | import { connect } from 'react-redux'; 19 | import { bindActionCreators } from 'redux'; 20 | 21 | import * as session from 'MobileApp/src/services/session'; 22 | import * as usersActionCreators from 'MobileApp/src/data/users/actions'; 23 | import * as usersSelectors from 'MobileApp/src/data/users/selectors'; 24 | 25 | const styles = StyleSheet.create({ 26 | container: { 27 | flex: 1, 28 | }, 29 | }); 30 | 31 | const renderList = () => { 32 | const items = usersSelectors.getAll(); 33 | if (items.length === 0) { 34 | return ( 35 | 36 | ); 37 | } 38 | 39 | return ( 40 | 41 | {items.map(item => ( 42 | 43 | {item.firstName} 44 | {item.email} 45 | 46 | ))} 47 | 48 | ); 49 | }; 50 | 51 | class Users extends Component { 52 | static propTypes = { 53 | navigator: PropTypes.shape({ 54 | getCurrentRoutes: PropTypes.func, 55 | jumpTo: PropTypes.func, 56 | }), 57 | actions: PropTypes.shape({ 58 | users: PropTypes.object, 59 | }), 60 | services: PropTypes.shape({ 61 | routeHistory: PropTypes.object, 62 | }), 63 | data: PropTypes.shape({ 64 | users: PropTypes.object, 65 | }), 66 | } 67 | 68 | componentDidMount() { 69 | this.tryFetch(); 70 | } 71 | 72 | componentDidUpdate() { 73 | this.tryFetch(); 74 | } 75 | 76 | onPressLogout() { 77 | session.revoke().then(() => { 78 | const routeStack = this.props.navigator.getCurrentRoutes(); 79 | this.props.navigator.jumpTo(routeStack[0]); 80 | this.props.actions.users.empty(); 81 | }); 82 | } 83 | 84 | tryFetch() { 85 | // Fetch users when the scene becomes active 86 | const { items } = this.props.services.routeHistory; 87 | if (Object.keys(this.props.data.users.items).length === 0 && 88 | items.length > 0 && items[items.length - 1].name === 'Users') { 89 | this.props.actions.users.get(); 90 | } 91 | } 92 | 93 | render() { 94 | return ( 95 | 96 | 97 |
98 | 104 | Users 105 |
106 | 107 | {renderList()} 108 | 109 |
110 |
111 | ); 112 | } 113 | } 114 | 115 | export default connect(state => ({ 116 | data: { 117 | users: state.data.users, 118 | }, 119 | services: { 120 | routeHistory: state.services.routeHistory, 121 | }, 122 | }), dispatch => ({ 123 | actions: { 124 | users: bindActionCreators(usersActionCreators, dispatch), 125 | }, 126 | }))(Users); 127 | -------------------------------------------------------------------------------- /client/MobileApp/src/scenes/Splash/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | StyleSheet, 4 | } from 'react-native'; 5 | import { 6 | Container, 7 | Spinner, 8 | View, 9 | } from 'native-base'; 10 | 11 | const styles = StyleSheet.create({ 12 | container: { 13 | paddingTop: 20, 14 | flex: 1, 15 | }, 16 | }); 17 | 18 | const Splash = () => ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | export default Splash; 27 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/api/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | clientId: '8puWuJWZYls1Ylawxm6CMiYREhsGGSyw', 3 | url: 'http://localhost:1337', 4 | }; 5 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/api/index.js: -------------------------------------------------------------------------------- 1 | /* global fetch */ 2 | 3 | import fetchival from 'fetchival'; 4 | import _ from 'lodash'; 5 | 6 | import * as sessionSelectors from 'MobileApp/src/services/session/selectors'; 7 | import apiConfig from './config'; 8 | 9 | export const exceptionExtractError = (exception) => { 10 | if (!exception.Errors) return false; 11 | let error = false; 12 | const errorKeys = Object.keys(exception.Errors); 13 | if (errorKeys.length > 0) { 14 | error = exception.Errors[errorKeys[0]][0].message; 15 | } 16 | return error; 17 | }; 18 | 19 | export const fetchApi = (endPoint, payload = {}, method = 'get', headers = {}) => { 20 | const accessToken = sessionSelectors.get().tokens.access.value; 21 | return fetchival(`${apiConfig.url}${endPoint}`, { 22 | headers: _.pickBy({ 23 | ...(accessToken ? { 24 | Authorization: `Bearer ${accessToken}`, 25 | } : { 26 | 'Client-ID': apiConfig.clientId, 27 | }), 28 | ...headers, 29 | }, item => !_.isEmpty(item)), 30 | })[method.toLowerCase()](payload) 31 | .catch((e) => { 32 | if (e.response && e.response.json) { 33 | e.response.json().then((json) => { 34 | if (json) throw json; 35 | throw e; 36 | }); 37 | } else { 38 | throw e; 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/persist/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const UPDATE = 'persist/UPDATE'; 2 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/persist/actions.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | export const update = payload => ({ 4 | type: actionTypes.UPDATE, 5 | payload, 6 | }); 7 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/persist/reducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | export const initialState = { 4 | isHydrated: false, 5 | }; 6 | 7 | 8 | export function reducer(state = initialState, action) { 9 | switch (action.type) { 10 | case actionTypes.UPDATE: 11 | return action.payload; 12 | default: 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as routeHistoryReducer } from './routeHistory/reducer'; 3 | import { reducer as sessionReducer } from './session/reducer'; 4 | import { reducer as persistReducer } from './persist/reducer'; 5 | 6 | export const reducer = combineReducers({ 7 | routeHistory: routeHistoryReducer, 8 | session: sessionReducer, 9 | persist: persistReducer, 10 | }); 11 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/routeHistory/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const PUSH = 'routeHistory/PUSH'; 2 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/routeHistory/actions.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | export const push = route => ({ 4 | type: actionTypes.PUSH, 5 | route, 6 | }); 7 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/routeHistory/reducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | const HISTORY_SIZE = 10; 4 | 5 | export const initialState = { 6 | items: [], 7 | }; 8 | 9 | export const reducer = (state = initialState, action) => { 10 | switch (action.type) { 11 | case actionTypes.PUSH: 12 | return { 13 | ...state, 14 | items: [...state.items.slice(-HISTORY_SIZE), action.route], 15 | }; 16 | default: 17 | return state; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const UPDATE = 'session/UPDATE'; 2 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/actions.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | export const update = session => ({ 4 | type: actionTypes.UPDATE, 5 | session, 6 | }); 7 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/api.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | import { fetchApi } from 'MobileApp/src/services/api'; 3 | import apiConfig from 'MobileApp/src/services/api/config'; 4 | 5 | const endPoints = { 6 | authenticate: '/users/auth', 7 | revoke: '/users/auth/revoke', 8 | refresh: '/users/auth/refresh', 9 | }; 10 | 11 | export const authenticate = (email, password) => fetchApi(endPoints.authenticate, {}, 'post', { 12 | Authorization: `Basic ${new Buffer(`${email}:${password}`).toString('base64')}`, 13 | }); 14 | 15 | export const refresh = (token, user) => fetchApi(endPoints.refresh, { token, user }, 'post', { 16 | 'Client-ID': apiConfig.clientId, 17 | Authorization: null, 18 | }); 19 | 20 | export const revoke = tokens => fetchApi(endPoints.revoke, { tokens }, 'post'); 21 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/index.js: -------------------------------------------------------------------------------- 1 | import store from 'MobileApp/src/store'; 2 | 3 | import * as api from './api'; 4 | import * as selectors from './selectors'; 5 | import * as actionCreators from './actions'; 6 | import { initialState } from './reducer'; 7 | 8 | const SESSION_TIMEOUT_THRESHOLD = 300; // Will refresh the access token 5 minutes before it expires 9 | 10 | let sessionTimeout = null; 11 | 12 | const setSessionTimeout = (duration) => { 13 | clearTimeout(sessionTimeout); 14 | sessionTimeout = setTimeout( 15 | refreshToken, // eslint-disable-line no-use-before-define 16 | (duration - SESSION_TIMEOUT_THRESHOLD) * 1000 17 | ); 18 | }; 19 | 20 | const clearSession = () => { 21 | clearTimeout(sessionTimeout); 22 | store.dispatch(actionCreators.update(initialState)); 23 | }; 24 | 25 | const onRequestSuccess = (response) => { 26 | const tokens = response.tokens.reduce((prev, item) => ({ 27 | ...prev, 28 | [item.type]: item, 29 | }), {}); 30 | store.dispatch(actionCreators.update({ tokens, user: response.user })); 31 | setSessionTimeout(tokens.access.expiresIn); 32 | }; 33 | 34 | const onRequestFailed = (exception) => { 35 | clearSession(); 36 | throw exception; 37 | }; 38 | 39 | export const refreshToken = () => { 40 | const session = selectors.get(); 41 | 42 | if (!session.tokens.refresh.value || !session.user.id) { 43 | return Promise.reject(); 44 | } 45 | 46 | return api.refresh(session.tokens.refresh, session.user) 47 | .then(onRequestSuccess) 48 | .catch(onRequestFailed); 49 | }; 50 | 51 | export const authenticate = (email, password) => 52 | api.authenticate(email, password) 53 | .then(onRequestSuccess) 54 | .catch(onRequestFailed); 55 | 56 | export const revoke = () => { 57 | const session = selectors.get(); 58 | return api.revoke(Object.keys(session.tokens).map(tokenKey => ({ 59 | type: session.tokens[tokenKey].type, 60 | value: session.tokens[tokenKey].value, 61 | }))) 62 | .then(clearSession()) 63 | .catch(() => {}); 64 | }; 65 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/reducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | 3 | export const initialState = { 4 | tokens: { 5 | access: { 6 | type: null, 7 | value: null, 8 | expiresIn: null, 9 | }, 10 | refresh: { 11 | type: null, 12 | value: null, 13 | }, 14 | }, 15 | user: { 16 | id: null, 17 | }, 18 | }; 19 | 20 | export const reducer = (state = initialState, action) => { 21 | switch (action.type) { 22 | case actionTypes.UPDATE: 23 | return { 24 | ...action.session, 25 | }; 26 | default: 27 | return state; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /client/MobileApp/src/services/session/selectors.js: -------------------------------------------------------------------------------- 1 | import store from 'MobileApp/src/store'; 2 | 3 | export const get = () => store.getState().services.session; 4 | -------------------------------------------------------------------------------- /client/MobileApp/src/store.js: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from 'react-native'; 2 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import devTools from 'remote-redux-devtools'; 5 | import { persistStore, autoRehydrate } from 'redux-persist'; 6 | import createFilter from 'redux-persist-transform-filter'; 7 | 8 | import { reducer as dataReducer } from './data/reducer'; 9 | import { reducer as servicesReducer } from './services/reducer'; 10 | import * as persistActionCreators from './services/persist/actions'; 11 | 12 | const appReducer = combineReducers({ 13 | services: servicesReducer, 14 | data: dataReducer, 15 | }); 16 | 17 | const enhancer = compose( 18 | applyMiddleware( 19 | thunk, 20 | ), 21 | devTools() 22 | ); 23 | 24 | const store = createStore( 25 | appReducer, 26 | enhancer, 27 | autoRehydrate(), 28 | ); 29 | 30 | const saveAndLoadSessionFilter = createFilter( 31 | 'services', 32 | ['session'], 33 | ['session'] 34 | ); 35 | 36 | export const persist = persistStore(store, { 37 | storage: AsyncStorage, 38 | blacklist: ['data'], 39 | transforms: [saveAndLoadSessionFilter], 40 | }, () => store.dispatch(persistActionCreators.update({ isHydrated: true }))); 41 | 42 | export default store; 43 | -------------------------------------------------------------------------------- /server/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tabs 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": false, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "extends": "airbnb-base", 9 | 10 | "rules": { 11 | "indent": [2, "tab", {"SwitchCase": 1}], 12 | "no-tabs": 0, 13 | "consistent-return": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | 21 | 22 | 23 | ################################################ 24 | # Local Configuration 25 | # 26 | # Explicitly ignore files which contain: 27 | # 28 | # 1. Sensitive information you'd rather not push to 29 | # your git repository. 30 | # e.g., your personal API keys or passwords. 31 | # 32 | # 2. Environment-specific configuration 33 | # Basically, anything that would be annoying 34 | # to have to change every time you do a 35 | # `git pull` 36 | # e.g., your local development database, or 37 | # the S3 bucket you're using for file uploads 38 | # development. 39 | # 40 | ################################################ 41 | 42 | config/local.js 43 | 44 | 45 | 46 | 47 | 48 | ################################################ 49 | # Dependencies 50 | # 51 | # When releasing a production app, you may 52 | # consider including your node_modules and 53 | # bower_components directory in your git repo, 54 | # but during development, its best to exclude it, 55 | # since different developers may be working on 56 | # different kernels, where dependencies would 57 | # need to be recompiled anyway. 58 | # 59 | # More on that here about node_modules dir: 60 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 61 | # (credit Mikeal Rogers, @mikeal) 62 | # 63 | # About bower_components dir, you can see this: 64 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 65 | # (credit Addy Osmani, @addyosmani) 66 | # 67 | ################################################ 68 | 69 | node_modules 70 | bower_components 71 | 72 | 73 | 74 | 75 | ################################################ 76 | # Sails.js / Waterline / Grunt 77 | # 78 | # Files generated by Sails and Grunt, or related 79 | # tasks and adapters. 80 | ################################################ 81 | dump.rdb 82 | 83 | 84 | 85 | 86 | 87 | ################################################ 88 | # Node.js / NPM 89 | # 90 | # Common files generated by Node, NPM, and the 91 | # related ecosystem. 92 | ################################################ 93 | lib-cov 94 | *.seed 95 | *.log 96 | *.out 97 | *.pid 98 | npm-debug.log 99 | 100 | 101 | 102 | 103 | 104 | ################################################ 105 | # Miscellaneous 106 | # 107 | # Common files generated by text editors, 108 | # operating systems, file systems, etc. 109 | ################################################ 110 | 111 | *~ 112 | *# 113 | .DS_STORE 114 | .netbeans 115 | nbproject 116 | .idea 117 | .node_history 118 | -------------------------------------------------------------------------------- /server/.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "modules": {} 4 | }, 5 | "hooks": { 6 | "grunt": false 7 | } 8 | } -------------------------------------------------------------------------------- /server/.tmp/localDiskDb.db: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "users": [ 4 | { 5 | "email": "user1@facebook.com", 6 | "firstName": "User 1", 7 | "password": "$2a$10$SkI3zAT5G1ntxXjAJuHiZeo9tSqWPDpZaESaMhcgsf9LCmcwibwN2", 8 | "createdAt": "2016-11-06T09:42:15.376Z", 9 | "updatedAt": "2016-11-06T09:42:15.376Z", 10 | "id": 1 11 | }, 12 | { 13 | "email": "user2@facebook.com", 14 | "firstName": "User 2", 15 | "password": "$2a$10$SkI3zAT5G1ntxXjAJuHiZeo9tSqWPDpZaESaMhcgsf9LCmcwibwN2", 16 | "createdAt": "2016-11-06T09:42:15.376Z", 17 | "updatedAt": "2016-11-06T09:42:15.376Z", 18 | "id": 2 19 | } 20 | ], 21 | "tokens": [], 22 | "clients": [ 23 | { 24 | "id": "8puWuJWZYls1Ylawxm6CMiYREhsGGSyw", 25 | "name": "React-Native client", 26 | "createdAt": "2016-11-06T14:18:06.096Z" 27 | } 28 | ] 29 | }, 30 | "schema": { 31 | "users": { 32 | "email": { 33 | "unique": true, 34 | "type": "string" 35 | }, 36 | "password": { 37 | "type": "string" 38 | }, 39 | "id": { 40 | "type": "integer", 41 | "autoIncrement": true, 42 | "primaryKey": true, 43 | "unique": true 44 | }, 45 | "createdAt": { 46 | "type": "datetime" 47 | }, 48 | "updatedAt": { 49 | "type": "datetime" 50 | } 51 | }, 52 | "tokens": { 53 | "user": { 54 | "type": "integer", 55 | "model": "users", 56 | "foreignKey": true, 57 | "alias": "user" 58 | }, 59 | "value": { 60 | "type": "string" 61 | }, 62 | "type": { 63 | "type": "string", 64 | "enum": [ 65 | "access", 66 | "refresh" 67 | ] 68 | }, 69 | "expiresAt": { 70 | "type": "date" 71 | }, 72 | "id": { 73 | "type": "integer", 74 | "autoIncrement": true, 75 | "primaryKey": true, 76 | "unique": true 77 | }, 78 | "createdAt": { 79 | "type": "datetime" 80 | } 81 | }, 82 | "clients": { 83 | "id": { 84 | "type": "string", 85 | "primaryKey": true, 86 | "unique": true 87 | }, 88 | "name": { 89 | "type": "string" 90 | }, 91 | "createdAt": { 92 | "type": "datetime" 93 | } 94 | } 95 | }, 96 | "counters": { 97 | "users": { 98 | "id": 2 99 | }, 100 | "tokens": {}, 101 | "clients": {} 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /server/api/controllers/ClientsController.js: -------------------------------------------------------------------------------- 1 | /* global Clients */ 2 | /** 3 | * ClientsController 4 | */ 5 | 6 | module.exports = { 7 | create(req, res) { 8 | Clients.add(req.allParams(), (err, client) => { 9 | if (err) { 10 | return res.negotiate(err); 11 | } 12 | return res.created({ client }); 13 | }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /server/api/controllers/UsersAuthController.js: -------------------------------------------------------------------------------- 1 | /* global Users, Tokens */ 2 | /** 3 | * UsersAuthController 4 | */ 5 | 6 | const passport = require('passport'); 7 | const moment = require('moment'); 8 | 9 | const expiresIn = expiresAt => 10 | Math.round(moment.duration( 11 | moment(expiresAt).diff(moment()) 12 | ).asSeconds()); 13 | 14 | const formatTokenResponse = (accessToken, refreshToken, user) => ({ 15 | tokens: [{ 16 | type: 'access', 17 | value: accessToken.value, 18 | expiresIn: expiresIn(accessToken.expiresAt), 19 | }, { 20 | type: 'refresh', 21 | value: refreshToken.value, 22 | }], 23 | user: { 24 | id: user.id, 25 | }, 26 | }); 27 | 28 | module.exports = { 29 | login(req, res) { 30 | passport.authenticate(['basic'], { session: false }, (authErr, user) => { 31 | if (authErr || !user) { 32 | return res.unauthorized(); 33 | } 34 | 35 | return Tokens.findOrAdd({ 36 | user: user.id, 37 | type: 'access', 38 | }, (accessTokenErr, accessToken) => { 39 | if (accessTokenErr) { 40 | return res.negotiate(accessTokenErr); 41 | } 42 | 43 | return Tokens.findOrAdd({ 44 | user: user.id, 45 | type: 'refresh', 46 | }, (refreshTokenErr, refreshToken) => { 47 | if (refreshTokenErr) { 48 | return res.negotiate(refreshTokenErr); 49 | } 50 | return res.ok(formatTokenResponse(accessToken, refreshToken, user)); 51 | }); 52 | }); 53 | })(req, res); 54 | }, 55 | 56 | refresh(req, res) { 57 | const params = req.allParams(); 58 | 59 | // Verify the refresh token is assigned to the user 60 | return Tokens.findOne({ 61 | user: params.user.id, 62 | value: params.token.value, 63 | type: 'refresh', 64 | }).exec((refreshTokenErr) => { 65 | if (refreshTokenErr) { 66 | return res.unauthorized(); 67 | } 68 | 69 | // Destroy the current access token 70 | Tokens.destroy({ 71 | user: params.user.id, 72 | type: 'access', 73 | }, (destroyErr) => { 74 | if (destroyErr) { 75 | return res.negotiate(destroyErr); 76 | } 77 | 78 | // Create a new access token 79 | return Tokens.findOrAdd({ 80 | user: params.user, 81 | type: 'access', 82 | }, (accessTokenErr, accessToken) => { 83 | if (accessTokenErr) { 84 | return res.negotiate(accessTokenErr); 85 | } 86 | return res.ok(formatTokenResponse(accessToken, params.token, params.user)); 87 | }); 88 | }); 89 | }); 90 | }, 91 | 92 | revoke(req, res) { 93 | const params = req.allParams(); 94 | if (!params.tokens || !params.tokens.length) { 95 | return res.badRequest(); 96 | } 97 | var counter = 0; 98 | 99 | params.tokens.forEach((token) => { 100 | Tokens.destroy({ 101 | value: token.value, 102 | type: token.type, 103 | user: req.query.accessUser.id, 104 | }, (err) => { 105 | counter += 1; 106 | if (err) { 107 | return res.error(); 108 | } else if (counter === params.tokens.length) { 109 | return res.ok(); 110 | } 111 | }); 112 | }); 113 | }, 114 | }; 115 | -------------------------------------------------------------------------------- /server/api/controllers/UsersController.js: -------------------------------------------------------------------------------- 1 | /* global Users */ 2 | /** 3 | * UsersController 4 | */ 5 | 6 | module.exports = { 7 | create(req, res) { 8 | Users.add(req.allParams(), (err, user) => { 9 | if (err) { 10 | return res.negotiate(err); 11 | } 12 | return res.created({ user }); 13 | }); 14 | }, 15 | 16 | getAll(req, res) { 17 | Users.find().exec((err, users) => { 18 | if (err) { 19 | return res.negotiate(err); 20 | } 21 | return res.ok({ users }); 22 | }); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /server/api/models/Clients.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clients.js 3 | */ 4 | 5 | const uid = require('rand-token').uid; 6 | 7 | const ID_LENGTH = 32; 8 | 9 | module.exports = { 10 | 11 | autoPK: false, 12 | 13 | autoUpdatedAt: false, 14 | 15 | attributes: { 16 | id: { 17 | type: 'string', 18 | primaryKey: true, 19 | required: true, 20 | }, 21 | 22 | name: { 23 | type: 'string', 24 | required: true, 25 | }, 26 | }, 27 | 28 | add(attrs, next) { 29 | return this.create({ 30 | id: uid(ID_LENGTH), 31 | name: String(attrs.name).trim(), 32 | }).exec(next); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /server/api/models/Tokens.js: -------------------------------------------------------------------------------- 1 | /* global sails */ 2 | /** 3 | * Tokens.js 4 | */ 5 | 6 | const randToken = require('rand-token'); 7 | const moment = require('moment'); 8 | 9 | module.exports = { 10 | 11 | autoUpdatedAt: false, 12 | 13 | attributes: { 14 | user: { 15 | model: 'users', 16 | }, 17 | 18 | value: 'string', 19 | 20 | type: { 21 | type: 'string', 22 | enum: ['access', 'refresh'], 23 | }, 24 | 25 | expiresAt: 'date', 26 | }, 27 | 28 | findOrAdd(attrs, next) { 29 | const tokenConfig = sails.config.oauth.tokens[attrs.type]; 30 | const token = Object.assign({}, attrs, { 31 | value: randToken.generate(tokenConfig.length), 32 | expiresAt: moment().utc().add(tokenConfig.life, 'seconds').toDate(), 33 | }); 34 | 35 | // Destroy user tokens about to expire 36 | return this.destroy(Object.assign({}, attrs, { 37 | expiresAt: { 38 | '<=': moment().utc().add(5, 'minutes').toDate(), 39 | }, 40 | }), (destroyErr) => { 41 | if (destroyErr) { 42 | return next(destroyErr); 43 | } 44 | // Restore existing token or create a new one 45 | this.findOrCreate(attrs, token).exec((createErr, userToken) => { 46 | if (createErr) { 47 | return next(createErr); 48 | } 49 | return next(null, userToken); 50 | }); 51 | }); 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /server/api/models/Users.js: -------------------------------------------------------------------------------- 1 | /* global PasswordService */ 2 | /** 3 | * Users.js 4 | */ 5 | 6 | const PASSWORD_MIN_LENGTH = 8; 7 | const PASSWORD_MAX_LENGTH = 30; 8 | 9 | module.exports = { 10 | attributes: { 11 | firstName: { 12 | required: true, 13 | }, 14 | 15 | email: { 16 | required: true, 17 | unique: true, 18 | type: 'email', 19 | }, 20 | 21 | password: { 22 | required: true, 23 | minLength: PASSWORD_MIN_LENGTH, 24 | maxLength: PASSWORD_MAX_LENGTH, 25 | type: 'string', 26 | }, 27 | 28 | // Override toJSON method to remove password from API 29 | toJSON() { 30 | const obj = this.toObject(); 31 | delete obj.password; 32 | return obj; 33 | }, 34 | }, 35 | 36 | validationMessages: { 37 | firstName: { 38 | required: 'First name is required.', 39 | }, 40 | email: { 41 | required: 'Email address is required.', 42 | email: 'Email address is not valid.', 43 | unique: 'Email address already exists.', 44 | }, 45 | password: { 46 | required: 'Password is required.', 47 | minLength: `Password is too short (min ${PASSWORD_MIN_LENGTH} characters).`, 48 | maxLength: `Password is too long (max ${PASSWORD_MAX_LENGTH} characters).`, 49 | }, 50 | }, 51 | 52 | beforeCreate(attrs, next) { 53 | PasswordService.encryptPassword(attrs.password).then((password) => { 54 | attrs.password = password; // This is the only way to assign the new encrypted password 55 | next(); 56 | }); 57 | }, 58 | 59 | add(attrs, next) { 60 | const payload = { 61 | firstName: String(attrs.firstName).trim(), 62 | email: String(attrs.email).trim(), 63 | password: String(attrs.password).trim(), 64 | }; 65 | return this.create(payload).exec(next); 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /server/api/policies/hasClientId.js: -------------------------------------------------------------------------------- 1 | /* global sails, Clients */ 2 | /** 3 | * hasClientId policy 4 | */ 5 | 6 | module.exports = (req, res, next) => { 7 | Clients.find(req.headers['client-id']).exec((err) => { 8 | if (err) { 9 | return res.unauthorized(); 10 | } 11 | return next(); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /server/api/policies/hasOAuthBearer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * hasOAuthBearer policy 3 | */ 4 | 5 | const passport = require('passport'); 6 | 7 | module.exports = (req, res, next) => { 8 | const userReq = Object.assign({}, req); 9 | delete userReq.query.accessToken; 10 | return passport.authenticate('bearer', (err, user) => { 11 | if (err) { 12 | return res.negotiate(err); 13 | } else if (!user) { 14 | return res.unauthorized(); 15 | } 16 | userReq.query.accessUser = user; 17 | return next(); 18 | })(userReq, res); 19 | }; 20 | -------------------------------------------------------------------------------- /server/api/policies/hasRefreshToken.js: -------------------------------------------------------------------------------- 1 | /* global Tokens */ 2 | /** 3 | * hasRefreshToken policy 4 | */ 5 | 6 | module.exports = (req, res, next) => { 7 | const params = req.allParams(); 8 | if (!params.token || params.token.type !== 'refresh' || !params.user.id) { 9 | return res.unauthorized(); 10 | } 11 | 12 | Tokens.findOne({ 13 | type: params.token.type, 14 | value: params.token.value, 15 | user: params.user.id, 16 | expiresAt: { '>=': new Date() }, 17 | }, (err, token) => { 18 | if (err) { 19 | return res.negotiate(err); 20 | } else if (!token) { 21 | return res.unauthorized(); 22 | } 23 | return next(); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /server/api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 400 (Bad Request) Handler 3 | * 4 | * Usage: 5 | * return res.badRequest(); 6 | * return res.badRequest(data); 7 | * return res.badRequest(data, 'some/specific/badRequest/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.badRequest( 12 | * 'Please choose a valid `password` (6-12 characters)', 13 | * 'trial/signup' 14 | * ); 15 | * ``` 16 | */ 17 | 18 | module.exports = function badRequest(data, options) { 19 | 20 | // Get access to `req`, `res`, & `sails` 21 | var req = this.req; 22 | var res = this.res; 23 | var sails = req._sails; 24 | 25 | // Set status code 26 | res.status(400); 27 | 28 | // Log error to console 29 | if (data !== undefined) { 30 | sails.log.verbose('Sending 400 ("Bad Request") response: \n',data); 31 | } 32 | else sails.log.verbose('Sending 400 ("Bad Request") response'); 33 | 34 | // Only include errors in response if application environment 35 | // is not set to 'production'. In production, we shouldn't 36 | // send back any identifying information about errors. 37 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 38 | data = undefined; 39 | } 40 | 41 | // If the user-agent wants JSON, always respond with JSON 42 | // If views are disabled, revert to json 43 | if (req.wantsJSON || sails.config.hooks.views === false) { 44 | return res.jsonx(data); 45 | } 46 | 47 | // If second argument is a string, we take that to mean it refers to a view. 48 | // If it was omitted, use an empty object (`{}`) 49 | options = (typeof options === 'string') ? { view: options } : options || {}; 50 | 51 | // Attempt to prettify data for views, if it's a non-error object 52 | var viewData = data; 53 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 54 | try { 55 | viewData = require('util').inspect(data, {depth: null}); 56 | } 57 | catch(e) { 58 | viewData = undefined; 59 | } 60 | } 61 | 62 | // If a view was provided in options, serve it. 63 | // Otherwise try to guess an appropriate view, or if that doesn't 64 | // work, just send JSON. 65 | if (options.view) { 66 | return res.view(options.view, { data: viewData, title: 'Bad Request' }); 67 | } 68 | 69 | // If no second argument provided, try to serve the implied view, 70 | // but fall back to sending JSON(P) if no view can be inferred. 71 | else return res.guessView({ data: viewData, title: 'Bad Request' }, function couldNotGuessView () { 72 | return res.jsonx(data); 73 | }); 74 | 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /server/api/responses/created.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 201 (CREATED) Response 3 | * 4 | * Usage: 5 | * return res.created(); 6 | * return res.created(data); 7 | * return res.created(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function created (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.created() :: Sending 201 ("CREATED") response'); 22 | 23 | // Set status code 24 | res.status(201); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false) { 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string') ? { view: options } : options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 39 | try { 40 | viewData = require('util').inspect(data, {depth: null}); 41 | } 42 | catch(e) { 43 | viewData = undefined; 44 | } 45 | } 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: viewData, title: 'Created' }); 52 | } 53 | 54 | // If no second argument provided, try to serve the implied view, 55 | // but fall back to sending JSON(P) if no view can be inferred. 56 | else return res.guessView({ data: viewData, title: 'Created' }, function couldNotGuessView () { 57 | return res.jsonx(data); 58 | }); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /server/api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 403 (Forbidden) Handler 3 | * 4 | * Usage: 5 | * return res.forbidden(); 6 | * return res.forbidden(err); 7 | * return res.forbidden(err, 'some/specific/forbidden/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.forbidden('Access denied.'); 12 | * ``` 13 | */ 14 | 15 | module.exports = function forbidden (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(403); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.verbose('Sending 403 ("Forbidden") response: \n',data); 28 | } 29 | else sails.log.verbose('Sending 403 ("Forbidden") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Forbidden' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('403', { data: viewData, title: 'Forbidden' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /server/api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 (Not Found) Handler 3 | * 4 | * Usage: 5 | * return res.notFound(); 6 | * return res.notFound(err); 7 | * return res.notFound(err, 'some/specific/notfound/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.notFound(); 12 | * ``` 13 | * 14 | * NOTE: 15 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 16 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 17 | * automatically. 18 | */ 19 | 20 | module.exports = function notFound (data, options) { 21 | 22 | // Get access to `req`, `res`, & `sails` 23 | var req = this.req; 24 | var res = this.res; 25 | var sails = req._sails; 26 | 27 | // Set status code 28 | res.status(404); 29 | 30 | // Log error to console 31 | if (data !== undefined) { 32 | sails.log.verbose('Sending 404 ("Not Found") response: \n',data); 33 | } 34 | else sails.log.verbose('Sending 404 ("Not Found") response'); 35 | 36 | // Only include errors in response if application environment 37 | // is not set to 'production'. In production, we shouldn't 38 | // send back any identifying information about errors. 39 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 40 | data = undefined; 41 | } 42 | 43 | // If the user-agent wants JSON, always respond with JSON 44 | // If views are disabled, revert to json 45 | if (req.wantsJSON || sails.config.hooks.views === false) { 46 | return res.jsonx(data); 47 | } 48 | 49 | // If second argument is a string, we take that to mean it refers to a view. 50 | // If it was omitted, use an empty object (`{}`) 51 | options = (typeof options === 'string') ? { view: options } : options || {}; 52 | 53 | // Attempt to prettify data for views, if it's a non-error object 54 | var viewData = data; 55 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 56 | try { 57 | viewData = require('util').inspect(data, {depth: null}); 58 | } 59 | catch(e) { 60 | viewData = undefined; 61 | } 62 | } 63 | 64 | // If a view was provided in options, serve it. 65 | // Otherwise try to guess an appropriate view, or if that doesn't 66 | // work, just send JSON. 67 | if (options.view) { 68 | return res.view(options.view, { data: viewData, title: 'Not Found' }); 69 | } 70 | 71 | // If no second argument provided, try to serve the default view, 72 | // but fall back to sending JSON(P) if any errors occur. 73 | else return res.view('404', { data: viewData, title: 'Not Found' }, function (err, html) { 74 | 75 | // If a view error occured, fall back to JSON(P). 76 | if (err) { 77 | // 78 | // Additionally: 79 | // • If the view was missing, ignore the error but provide a verbose log. 80 | if (err.code === 'E_VIEW_FAILED') { 81 | sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err); 82 | } 83 | // Otherwise, if this was a more serious error, log to the console with the details. 84 | else { 85 | sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 86 | } 87 | return res.jsonx(data); 88 | } 89 | 90 | return res.send(html); 91 | }); 92 | 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /server/api/responses/ok.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 200 (OK) Response 3 | * 4 | * Usage: 5 | * return res.ok(); 6 | * return res.ok(data); 7 | * return res.ok(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function sendOK (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.ok() :: Sending 200 ("OK") response'); 22 | 23 | // Set status code 24 | res.status(data ? 200 : 204); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false) { 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string') ? { view: options } : options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 39 | try { 40 | viewData = require('util').inspect(data, {depth: null}); 41 | } 42 | catch(e) { 43 | viewData = undefined; 44 | } 45 | } 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: viewData, title: 'OK' }); 52 | } 53 | 54 | // If no second argument provided, try to serve the implied view, 55 | // but fall back to sending JSON(P) if no view can be inferred. 56 | else return res.guessView({ data: viewData, title: 'OK' }, function couldNotGuessView () { 57 | return res.jsonx(data); 58 | }); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /server/api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 500 (Server Error) Response 3 | * 4 | * Usage: 5 | * return res.serverError(); 6 | * return res.serverError(err); 7 | * return res.serverError(err, 'some/specific/error/view'); 8 | * 9 | * NOTE: 10 | * If something throws in a policy or controller, or an internal 11 | * error is encountered, Sails will call `res.serverError()` 12 | * automatically. 13 | */ 14 | 15 | module.exports = function serverError (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(500); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.error('Sending 500 ("Server Error") response: \n',data); 28 | } 29 | else sails.log.error('Sending empty 500 ("Server Error") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Server Error' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('500', { data: viewData, title: 'Server Error' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /server/api/responses/unauthorized.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 401 (Unauthorized) Handler 3 | * 4 | * Usage: 5 | * return res.unauthorized(); 6 | * return res.unauthorized(err); 7 | * return res.unauthorized(err, 'some/specific/unauthorized/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.unauthorized('Access denied.'); 12 | * ``` 13 | */ 14 | 15 | module.exports = function unauthorized (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(401); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.verbose('Sending 401 ("Unauthorized") response: \n',data); 28 | } 29 | else sails.log.verbose('Sending 401 ("Unauthorized") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Unauthorized' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('401', { data: viewData, title: 'Unauthorized' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.unauthorized() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.unauthorized() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /server/api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmngn/react-native-authentication/f1918a0c450a7bef337179ae18dff68e721b68e3/server/api/services/.gitkeep -------------------------------------------------------------------------------- /server/api/services/PasswordService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PasswordService 3 | */ 4 | 5 | const bcrypt = require('bcrypt'); 6 | 7 | module.exports = { 8 | encryptPassword(password) { 9 | return new Promise((resolve, reject) => { 10 | bcrypt.genSalt(10, (genSaltErr, salt) => { 11 | if (genSaltErr) return reject(genSaltErr); 12 | 13 | bcrypt.hash(password, salt, (hashErr, hash) => { 14 | if (hashErr) return reject(hashErr); 15 | resolve(hash); 16 | }); 17 | }); 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * Use `app.js` to run your app without `sails lift`. 5 | * To start the server, run: `node app.js`. 6 | * 7 | * This is handy in situations where the sails CLI is not relevant or useful. 8 | * 9 | * For example: 10 | * => `node app.js` 11 | * => `forever start app.js` 12 | * => `node debug app.js` 13 | * => `modulus deploy` 14 | * => `heroku scale` 15 | * 16 | * 17 | * The same command-line arguments are supported, e.g.: 18 | * `node app.js --silent --port=80 --prod` 19 | */ 20 | 21 | 22 | // Ensure we're in the project directory, so cwd-relative paths work as expected 23 | // no matter where we actually lift from. 24 | // > Note: This is not required in order to lift, but it is a convenient default. 25 | process.chdir(__dirname); 26 | 27 | // Attempt to import `sails`. 28 | var sails; 29 | try { 30 | sails = require('sails'); 31 | } catch (e) { 32 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.'); 33 | console.error('To do that, run `npm install sails`'); 34 | console.error(''); 35 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.'); 36 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,'); 37 | console.error('but if it doesn\'t, the app will run with the global sails instead!'); 38 | } 39 | 40 | // --• 41 | // Try to get `rc` dependency (for loading `.sailsrc` files). 42 | var rc; 43 | try { 44 | rc = require('rc'); 45 | } catch (e0) { 46 | try { 47 | rc = require('sails/node_modules/rc'); 48 | } catch (e1) { 49 | console.error('Could not find dependency: `rc`.'); 50 | console.error('Your `.sailsrc` file(s) will be ignored.'); 51 | console.error('To resolve this, run:'); 52 | console.error('npm install rc --save'); 53 | rc = function () { return {}; }; 54 | } 55 | } 56 | 57 | 58 | // Start server 59 | sails.lift(rc('sails')); 60 | -------------------------------------------------------------------------------- /server/config/blueprints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blueprint API Configuration 3 | * (sails.config.blueprints) 4 | * 5 | * These settings are for the global configuration of blueprint routes and 6 | * request options (which impact the behavior of blueprint actions). 7 | * 8 | * You may also override any of these settings on a per-controller basis 9 | * by defining a '_config' key in your controller definition, and assigning it 10 | * a configuration object with overrides for the settings in this file. 11 | * A lot of the configuration options below affect so-called "CRUD methods", 12 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 13 | * 14 | * It's important to realize that, even if you haven't defined these yourself, as long as 15 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 16 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 17 | * 18 | * For more information on the blueprint API, check out: 19 | * http://sailsjs.org/#!/documentation/reference/blueprint-api 20 | * 21 | * For more information on the settings in this file, see: 22 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.blueprints.html 23 | * 24 | */ 25 | 26 | module.exports.blueprints = { 27 | 28 | /*************************************************************************** 29 | * * 30 | * Action routes speed up the backend development workflow by * 31 | * eliminating the need to manually bind routes. When enabled, GET, POST, * 32 | * PUT, and DELETE routes will be generated for every one of a controller's * 33 | * actions. * 34 | * * 35 | * If an `index` action exists, additional naked routes will be created for * 36 | * it. Finally, all `actions` blueprints support an optional path * 37 | * parameter, `id`, for convenience. * 38 | * * 39 | * `actions` are enabled by default, and can be OK for production-- * 40 | * however, if you'd like to continue to use controller/action autorouting * 41 | * in a production deployment, you must take great care not to * 42 | * inadvertently expose unsafe/unintentional controller logic to GET * 43 | * requests. * 44 | * * 45 | ***************************************************************************/ 46 | 47 | // actions: true, 48 | 49 | /*************************************************************************** 50 | * * 51 | * RESTful routes (`sails.config.blueprints.rest`) * 52 | * * 53 | * REST blueprints are the automatically generated routes Sails uses to * 54 | * expose a conventional REST API on top of a controller's `find`, * 55 | * `create`, `update`, and `destroy` actions. * 56 | * * 57 | * For example, a BoatController with `rest` enabled generates the * 58 | * following routes: * 59 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: * 60 | * GET /boat -> BoatController.find * 61 | * GET /boat/:id -> BoatController.findOne * 62 | * POST /boat -> BoatController.create * 63 | * PUT /boat/:id -> BoatController.update * 64 | * DELETE /boat/:id -> BoatController.destroy * 65 | * * 66 | * `rest` blueprint routes are enabled by default, and are suitable for use * 67 | * in a production scenario, as long you take standard security precautions * 68 | * (combine w/ policies, etc.) * 69 | * * 70 | ***************************************************************************/ 71 | 72 | // rest: true, 73 | 74 | /*************************************************************************** 75 | * * 76 | * Shortcut routes are simple helpers to provide access to a * 77 | * controller's CRUD methods from your browser's URL bar. When enabled, * 78 | * GET, POST, PUT, and DELETE routes will be generated for the * 79 | * controller's`find`, `create`, `update`, and `destroy` actions. * 80 | * * 81 | * `shortcuts` are enabled by default, but should be disabled in * 82 | * production. * 83 | * * 84 | ***************************************************************************/ 85 | 86 | // shortcuts: true, 87 | 88 | /*************************************************************************** 89 | * * 90 | * An optional mount path for all blueprint routes on a controller, * 91 | * including `rest`, `actions`, and `shortcuts`. This allows you to take * 92 | * advantage of blueprint routing, even if you need to namespace your API * 93 | * methods. * 94 | * * 95 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 96 | * `sails.config.routes`) * 97 | * * 98 | ***************************************************************************/ 99 | 100 | // prefix: '', 101 | 102 | /*************************************************************************** 103 | * * 104 | * An optional mount path for all REST blueprint routes on a controller. * 105 | * And it do not include `actions` and `shortcuts` routes. * 106 | * This allows you to take advantage of REST blueprint routing, * 107 | * even if you need to namespace your RESTful API methods * 108 | * * 109 | ***************************************************************************/ 110 | 111 | // restPrefix: '', 112 | 113 | /*************************************************************************** 114 | * * 115 | * Whether to pluralize controller names in blueprint routes. * 116 | * * 117 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 118 | * `sails.config.routes`) * 119 | * * 120 | * For example, REST blueprints for `FooController` with `pluralize` * 121 | * enabled: * 122 | * GET /foos/:id? * 123 | * POST /foos * 124 | * PUT /foos/:id? * 125 | * DELETE /foos/:id? * 126 | * * 127 | ***************************************************************************/ 128 | 129 | // pluralize: false, 130 | 131 | /*************************************************************************** 132 | * * 133 | * Whether the blueprint controllers should populate model fetches with * 134 | * data from other models which are linked by associations * 135 | * * 136 | * If you have a lot of data in one-to-many associations, leaving this on * 137 | * may result in very heavy api calls * 138 | * * 139 | ***************************************************************************/ 140 | 141 | // populate: true, 142 | 143 | /**************************************************************************** 144 | * * 145 | * Whether to run Model.watch() in the find and findOne blueprint actions. * 146 | * Can be overridden on a per-model basis. * 147 | * * 148 | ****************************************************************************/ 149 | 150 | // autoWatch: true, 151 | 152 | /**************************************************************************** 153 | * * 154 | * The default number of records to show in the response from a "find" * 155 | * action. Doubles as the default size of populated arrays if populate is * 156 | * true. * 157 | * * 158 | ****************************************************************************/ 159 | 160 | // defaultLimit: 30 161 | 162 | }; 163 | -------------------------------------------------------------------------------- /server/config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.bootstrap.html 10 | */ 11 | 12 | module.exports.bootstrap = function(cb) { 13 | 14 | // It's very important to trigger this callback method when you are finished 15 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 16 | cb(); 17 | }; 18 | -------------------------------------------------------------------------------- /server/config/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.connections.html 20 | */ 21 | 22 | module.exports.connections = { 23 | 24 | /*************************************************************************** 25 | * * 26 | * Local disk storage for DEVELOPMENT ONLY * 27 | * * 28 | * Installed by default. * 29 | * * 30 | ***************************************************************************/ 31 | localDiskDb: { 32 | adapter: 'sails-disk' 33 | }, 34 | 35 | /*************************************************************************** 36 | * * 37 | * MySQL is the world's most popular relational database. * 38 | * http://en.wikipedia.org/wiki/MySQL * 39 | * * 40 | * Run: npm install sails-mysql * 41 | * * 42 | ***************************************************************************/ 43 | // someMysqlServer: { 44 | // adapter: 'sails-mysql', 45 | // host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 46 | // user: 'YOUR_MYSQL_USER', //optional 47 | // password: 'YOUR_MYSQL_PASSWORD', //optional 48 | // database: 'YOUR_MYSQL_DB' //optional 49 | // }, 50 | 51 | /*************************************************************************** 52 | * * 53 | * MongoDB is the leading NoSQL database. * 54 | * http://en.wikipedia.org/wiki/MongoDB * 55 | * * 56 | * Run: npm install sails-mongo * 57 | * * 58 | ***************************************************************************/ 59 | // someMongodbServer: { 60 | // adapter: 'sails-mongo', 61 | // host: 'localhost', 62 | // port: 27017, 63 | // user: 'username', //optional 64 | // password: 'password', //optional 65 | // database: 'your_mongo_db_name_here' //optional 66 | // }, 67 | 68 | /*************************************************************************** 69 | * * 70 | * PostgreSQL is another officially supported relational database. * 71 | * http://en.wikipedia.org/wiki/PostgreSQL * 72 | * * 73 | * Run: npm install sails-postgresql * 74 | * * 75 | * * 76 | ***************************************************************************/ 77 | // somePostgresqlServer: { 78 | // adapter: 'sails-postgresql', 79 | // host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', 80 | // user: 'YOUR_POSTGRES_USER', // optional 81 | // password: 'YOUR_POSTGRES_PASSWORD', // optional 82 | // database: 'YOUR_POSTGRES_DB' //optional 83 | // } 84 | 85 | 86 | /*************************************************************************** 87 | * * 88 | * More adapters: https://github.com/balderdashy/sails * 89 | * * 90 | ***************************************************************************/ 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /server/config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | allRoutes: true, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | credentials: true, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | headers: 'Accept, Content-Type, Authorization', 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /server/config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection Settings 3 | * (sails.config.csrf) 4 | * 5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 6 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 7 | * 8 | * When enabled, all non-GET requests to the Sails server must be accompanied by 9 | * a special token, identified as the '_csrf' parameter. 10 | * 11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 14 | * 15 | * This allows us to have certainty that our users' requests haven't been hijacked, 16 | * and that the requests they're making are intentional and legitimate. 17 | * 18 | * This token has a short-lived expiration timeline, and must be acquired by either: 19 | * 20 | * (a) For traditional view-driven web apps: 21 | * Fetching it from one of your views, where it may be accessed as 22 | * a local variable, e.g.: 23 | *
24 | * 25 | *
26 | * 27 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 28 | * Sending a GET request to the `/csrfToken` route, where it will be returned 29 | * as JSON, e.g.: 30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 31 | * 32 | * 33 | * Enabling this option requires managing the token in your front-end app. 34 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 36 | * 37 | * For more information on CSRF, check out: 38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 39 | * 40 | * For more information on this configuration file, including info on CSRF + CORS, see: 41 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.csrf.html 42 | * 43 | */ 44 | 45 | /**************************************************************************** 46 | * * 47 | * Enabled CSRF protection for your site? * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // module.exports.csrf = false; 52 | 53 | /**************************************************************************** 54 | * * 55 | * You may also specify more fine-grained settings for CSRF, including the * 56 | * domains which are allowed to request the CSRF token via AJAX. These * 57 | * settings override the general CORS settings in your config/cors.js file. * 58 | * * 59 | ****************************************************************************/ 60 | 61 | // module.exports.csrf = { 62 | // grantTokenViaAjax: true, 63 | // origin: '' 64 | // } 65 | -------------------------------------------------------------------------------- /server/config/env/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Development environment settings 3 | * 4 | * This file can include shared settings for a development team, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the development * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMongodbServer' 22 | // } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /server/config/env/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Production environment settings 3 | * 4 | * This file can include shared settings for a production environment, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the production * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'someMysqlServer' 22 | // }, 23 | 24 | /*************************************************************************** 25 | * Set the port in the production environment to 80 * 26 | ***************************************************************************/ 27 | 28 | // port: 80, 29 | 30 | /*************************************************************************** 31 | * Set the log level in production environment to "silent" * 32 | ***************************************************************************/ 33 | 34 | // log: { 35 | // level: "silent" 36 | // } 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /server/config/globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Variable Configuration 3 | * (sails.config.globals) 4 | * 5 | * Configure which global variables which will be exposed 6 | * automatically by Sails. 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.globals.html 10 | */ 11 | module.exports.globals = { 12 | 13 | /**************************************************************************** 14 | * * 15 | * Expose the lodash installed in Sails core as a global variable. If this * 16 | * is disabled, like any other node module you can always run npm install * 17 | * lodash --save, then var _ = require('lodash') at the top of any file. * 18 | * * 19 | ****************************************************************************/ 20 | 21 | // _: true, 22 | 23 | /**************************************************************************** 24 | * * 25 | * Expose the async installed in Sails core as a global variable. If this is * 26 | * disabled, like any other node module you can always run npm install async * 27 | * --save, then var async = require('async') at the top of any file. * 28 | * * 29 | ****************************************************************************/ 30 | 31 | // async: true, 32 | 33 | /**************************************************************************** 34 | * * 35 | * Expose the sails instance representing your app. If this is disabled, you * 36 | * can still get access via req._sails. * 37 | * * 38 | ****************************************************************************/ 39 | 40 | // sails: true, 41 | 42 | /**************************************************************************** 43 | * * 44 | * Expose each of your app's services as global variables (using their * 45 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js * 46 | * would have a globalId of NaturalLanguage by default. If this is disabled, * 47 | * you can still access your services via sails.services.* * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // services: true, 52 | 53 | /**************************************************************************** 54 | * * 55 | * Expose each of your app's models as global variables (using their * 56 | * "globalId"). E.g. a model defined in api/models/User.js would have a * 57 | * globalId of User by default. If this is disabled, you can still access * 58 | * your models via sails.models.*. * 59 | * * 60 | ****************************************************************************/ 61 | 62 | // models: true 63 | }; 64 | -------------------------------------------------------------------------------- /server/config/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP Server Settings 3 | * (sails.config.http) 4 | * 5 | * Configuration for the underlying HTTP server in Sails. 6 | * Only applies to HTTP requests (not WebSockets) 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html 10 | */ 11 | 12 | const passport = require('passport'); 13 | 14 | module.exports.http = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * Express middleware to use for every Sails request. To add custom * 19 | * middleware to the mix, add a function to the middleware config object and * 20 | * add its key to the "order" array. The $custom key is reserved for * 21 | * backwards-compatibility with Sails v0.9.x apps that use the * 22 | * `customMiddleware` config option. * 23 | * * 24 | ****************************************************************************/ 25 | 26 | middleware: { 27 | passportInit: passport.initialize(), 28 | 29 | /*************************************************************************** 30 | * * 31 | * The order in which middleware should be run for HTTP request. (the Sails * 32 | * router is invoked by the "router" middleware below.) * 33 | * * 34 | ***************************************************************************/ 35 | 36 | order: [ 37 | 'startRequestTimer', 38 | 'cookieParser', 39 | 'session', 40 | 'myRequestLogger', 41 | 'bodyParser', 42 | 'handleBodyParserError', 43 | 'compress', 44 | 'methodOverride', 45 | 'poweredBy', 46 | '$custom', 47 | 'passportInit', 48 | 'router', 49 | 'www', 50 | 'favicon', 51 | '404', 52 | '500', 53 | ], 54 | 55 | /**************************************************************************** 56 | * * 57 | * Example custom middleware; logs each request to the console. * 58 | * * 59 | ****************************************************************************/ 60 | 61 | // myRequestLogger: function (req, res, next) { 62 | // console.log("Requested :: ", req.method, req.url); 63 | // return next(); 64 | // } 65 | 66 | 67 | /*************************************************************************** 68 | * * 69 | * The body parser that will handle incoming multipart HTTP requests. By * 70 | * default as of v0.10, Sails uses * 71 | * [skipper](http://github.com/balderdashy/skipper). See * 72 | * http://www.senchalabs.org/connect/multipart.html for other options. * 73 | * * 74 | * Note that Sails uses an internal instance of Skipper by default; to * 75 | * override it and specify more options, make sure to "npm install skipper" * 76 | * in your project first. You can also specify a different body parser or * 77 | * a custom function with req, res and next parameters (just like any other * 78 | * middleware function). * 79 | * * 80 | ***************************************************************************/ 81 | 82 | // bodyParser: require('skipper')({strict: true}) 83 | 84 | }, 85 | 86 | /*************************************************************************** 87 | * * 88 | * The number of seconds to cache flat files on disk being served by * 89 | * Express static middleware (by default, these files are in `.tmp/public`) * 90 | * * 91 | * The HTTP static cache is only active in a 'production' environment, * 92 | * since that's the only time Express will cache flat-files. * 93 | * * 94 | ***************************************************************************/ 95 | 96 | // cache: 31557600000 97 | }; 98 | -------------------------------------------------------------------------------- /server/config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * (sails.config.i18n) 4 | * 5 | * If your app will touch people from all over the world, i18n (or internationalization) 6 | * may be an important part of your international strategy. 7 | * 8 | * 9 | * For more informationom i18n in Sails, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Internationalization 11 | * 12 | * For a complete list of i18n options, see: 13 | * https://github.com/mashpie/i18n-node#list-of-configuration-options 14 | * 15 | * 16 | */ 17 | 18 | module.exports.i18n = { 19 | 20 | /*************************************************************************** 21 | * * 22 | * Which locales are supported? * 23 | * * 24 | ***************************************************************************/ 25 | 26 | // locales: ['en', 'es', 'fr', 'de'], 27 | 28 | /**************************************************************************** 29 | * * 30 | * What is the default locale for the site? Note that this setting will be * 31 | * overridden for any request that sends an "Accept-Language" header (i.e. * 32 | * most browsers), but it's still useful if you need to localize the * 33 | * response for requests made by non-browser clients (e.g. cURL). * 34 | * * 35 | ****************************************************************************/ 36 | 37 | // defaultLocale: 'en', 38 | 39 | /**************************************************************************** 40 | * * 41 | * Automatically add new keys to locale (translation) files when they are * 42 | * encountered during a request? * 43 | * * 44 | ****************************************************************************/ 45 | 46 | // updateFiles: false, 47 | 48 | /**************************************************************************** 49 | * * 50 | * Path (relative to app root) of directory to store locale (translation) * 51 | * files in. * 52 | * * 53 | ****************************************************************************/ 54 | 55 | // localesDirectory: '/config/locales' 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /server/config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

<%= __('Welcome to PencilPals!') %>

23 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

24 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /server/config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Willkommen", 3 | "A brand new app.": "Eine neue App." 4 | } 5 | -------------------------------------------------------------------------------- /server/config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome", 3 | "A brand new app.": "A brand new app." 4 | } 5 | -------------------------------------------------------------------------------- /server/config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido", 3 | "A brand new app.": "Una nueva aplicación." 4 | } 5 | -------------------------------------------------------------------------------- /server/config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue", 3 | "A brand new app.": "Une toute nouvelle application." 4 | } 5 | -------------------------------------------------------------------------------- /server/config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Logging 11 | */ 12 | 13 | module.exports.log = { 14 | 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | 27 | 28 | log: { 29 | level: 'silly', 30 | filePath: 'logs/application.log', 31 | }, 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /server/config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#!/documentation/concepts/ORM 10 | */ 11 | 12 | module.exports.models = { 13 | 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | // connection: 'localDiskDb', 21 | 22 | /*************************************************************************** 23 | * * 24 | * How and whether Sails will attempt to automatically rebuild the * 25 | * tables/collections/etc. in your schema. * 26 | * * 27 | * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html * 28 | * * 29 | ***************************************************************************/ 30 | migrate: 'safe', 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /server/config/oauth.js: -------------------------------------------------------------------------------- 1 | module.exports.oauth = { 2 | tokens: { 3 | access: { 4 | length: 32, 5 | life: 3600, // 1 hour 6 | }, 7 | refresh: { 8 | length: 32, 9 | life: (3600 * 24 * 90), // 90 days 10 | }, 11 | }, 12 | clients: { 13 | '5746f32e39485d1103b31254': { 14 | name: 'Mobile app', 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/config/passport.js: -------------------------------------------------------------------------------- 1 | /* global Users, Tokens */ 2 | 3 | const bcrypt = require('bcrypt'); 4 | const moment = require('moment'); 5 | const passport = require('passport'); 6 | const BearerStrategy = require('passport-http-bearer').Strategy; 7 | const BasicStrategy = require('passport-http').BasicStrategy; 8 | 9 | passport.serializeUser((user, next) => 10 | next(null, user.id) 11 | ); 12 | 13 | passport.deserializeUser((id, next) => 14 | Users.findOne({ id }, (err, user) => { 15 | next(err, user); 16 | }) 17 | ); 18 | 19 | /** 20 | * BasicStrategy & ClientPasswordStrategy 21 | * 22 | * These strategies are used to authenticate registered OAuth clients. They are 23 | * employed to protect the `token` endpoint, which consumers use to obtain 24 | * access tokens. The OAuth 2.0 specification suggests that clients use the 25 | * HTTP Basic scheme to authenticate. Use of the client password strategy 26 | * allows clients to send the same credentials in the request body (as opposed 27 | * to the `Authorization` header). While this approach is not recommended by 28 | * the specification, in practice it is quite common. 29 | */ 30 | passport.use( 31 | new BasicStrategy( 32 | (email, password, next) => 33 | Users.findOne({ 34 | email, 35 | }).exec((findErr, user) => { 36 | if (findErr) { 37 | return next(findErr); 38 | } else if (!user) { 39 | return next(401); 40 | } 41 | 42 | return bcrypt.compare(password, user.password, (bcryptErr, res) => { 43 | if (bcryptErr) { 44 | return next(bcryptErr); 45 | } else if (!res) { 46 | return next(401); 47 | } 48 | return next(null, user); 49 | }); 50 | }) 51 | ) 52 | ); 53 | 54 | 55 | /** 56 | * BearerStrategy 57 | * 58 | * This strategy is used to authenticate users based on an access token (aka a 59 | * bearer token). The user must have previously authorized a client 60 | * application, which is issued an access token to make requests on behalf of 61 | * the authorizing user. 62 | */ 63 | passport.use( 64 | new BearerStrategy( 65 | (tokenValue, next) => { 66 | Tokens.findOne({ value: tokenValue, type: 'access' }, (findErr, userToken) => { 67 | if (findErr) { 68 | return next(findErr); 69 | } else if (!userToken) { 70 | return next(); 71 | } 72 | 73 | if (moment(userToken.expiresAt).unix() < moment().unix()) { 74 | Tokens.destroy({ value: tokenValue, type: 'access' }).exec(); 75 | return next(); 76 | } 77 | 78 | Users.findOne({ 79 | id: userToken.user, 80 | }).exec((userErr, user) => { 81 | if (userErr) { 82 | return next(userErr); 83 | } 84 | return next(null, user); 85 | }); 86 | }); 87 | } 88 | ) 89 | ); 90 | -------------------------------------------------------------------------------- /server/config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy Mappings 3 | * (sails.config.policies) 4 | * 5 | * Policies are simple functions which run **before** your controllers. 6 | * You can apply one or more policies to a given controller, or protect 7 | * its actions individually. 8 | * 9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 10 | * below by its filename, minus the extension, (e.g. "authenticated") 11 | * 12 | * For more information on how policies work, see: 13 | * http://sailsjs.org/#!/documentation/concepts/Policies 14 | * 15 | * For more information on configuring policies, check out: 16 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.policies.html 17 | */ 18 | 19 | 20 | module.exports.policies = { 21 | 22 | '*': 'hasOAuthBearer', 23 | 24 | ClientsController: { 25 | create: [], 26 | }, 27 | 28 | UsersController: { 29 | create: ['hasClientId'], 30 | }, 31 | 32 | UsersAuthController: { 33 | '*': 'hasClientId', 34 | refresh: ['hasClientId', 'hasRefreshToken'], 35 | revoke: ['hasOAuthBearer'], 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /server/config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | 'post /clients': 'ClientsController.create', 26 | 27 | 'post /users': { 28 | cors: { 29 | headers: 'Content-Type, Client-ID', 30 | }, 31 | controller: 'UsersController', 32 | action: 'create', 33 | }, 34 | 35 | 'post /users/auth': { 36 | cors: { 37 | headers: 'Content-Type, Client-ID, Authorization', 38 | }, 39 | controller: 'UsersAuthController', 40 | action: 'login', 41 | }, 42 | 'post /users/auth/refresh': { 43 | cors: { 44 | headers: 'Content-Type, Client-ID', 45 | }, 46 | controller: 'UsersAuthController', 47 | action: 'refresh', 48 | }, 49 | 50 | 'post /users/auth/revoke': 'UsersAuthController.revoke', 51 | 'get /users': 'UsersController.getAll', 52 | }; 53 | -------------------------------------------------------------------------------- /server/config/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Session Configuration 3 | * (sails.config.session) 4 | * 5 | * Sails session integration leans heavily on the great work already done by 6 | * Express, but also unifies Socket.io with the Connect session store. It uses 7 | * Connect's cookie parser to normalize configuration differences between Express 8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access 9 | * and auto-save to `req.session` with Socket.io the same way you would with Express. 10 | * 11 | * For more information on configuring the session, check out: 12 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.session.html 13 | */ 14 | 15 | module.exports.session = { 16 | 17 | /*************************************************************************** 18 | * * 19 | * Session secret is automatically generated when your new app is created * 20 | * Replace at your own risk in production-- you will invalidate the cookies * 21 | * of your users, forcing them to log in again. * 22 | * * 23 | ***************************************************************************/ 24 | secret: '13063557f59651acc3a806649ab3d8ba', 25 | 26 | 27 | /*************************************************************************** 28 | * * 29 | * Set the session cookie expire time The maxAge is set by milliseconds, * 30 | * the example below is for 24 hours * 31 | * * 32 | ***************************************************************************/ 33 | 34 | // cookie: { 35 | // maxAge: 24 * 60 * 60 * 1000 36 | // }, 37 | 38 | /*************************************************************************** 39 | * * 40 | * Uncomment the following lines to set up a Redis session store that can * 41 | * be shared across multiple Sails.js servers. * 42 | * * 43 | * Requires connect-redis (https://www.npmjs.com/package/connect-redis) * 44 | * * 45 | ***************************************************************************/ 46 | 47 | // adapter: 'redis', 48 | 49 | /*************************************************************************** 50 | * * 51 | * The following values are optional, if no options are set a redis * 52 | * instance running on localhost is expected. Read more about options at: * 53 | * * 54 | * https://github.com/visionmedia/connect-redis * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // host: 'localhost', 59 | // port: 6379, 60 | // ttl: , 61 | // db: 0, 62 | // pass: , 63 | // prefix: 'sess:', 64 | 65 | 66 | /*************************************************************************** 67 | * * 68 | * Uncomment the following lines to set up a MongoDB session store that can * 69 | * be shared across multiple Sails.js servers. * 70 | * * 71 | * Requires connect-mongo (https://www.npmjs.com/package/connect-mongo) * 72 | * Use version 0.8.2 with Node version <= 0.12 * 73 | * Use the latest version with Node >= 4.0 * 74 | * * 75 | ***************************************************************************/ 76 | 77 | // adapter: 'mongo', 78 | // url: 'mongodb://user:password@localhost:27017/dbname', // user, password and port optional 79 | 80 | /*************************************************************************** 81 | * * 82 | * Optional Values: * 83 | * * 84 | * See https://github.com/kcbanner/connect-mongo for more * 85 | * information about connect-mongo options. * 86 | * * 87 | * See http://bit.ly/mongooptions for more information about options * 88 | * available in `mongoOptions` * 89 | * * 90 | ***************************************************************************/ 91 | 92 | // collection: 'sessions', 93 | // stringify: true, 94 | // mongoOptions: { 95 | // server: { 96 | // ssl: true 97 | // } 98 | // } 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /server/config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | 13 | module.exports.sockets = { 14 | 15 | 16 | /*************************************************************************** 17 | * * 18 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 19 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 20 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 21 | * servers and throw them behind a load balancer. * 22 | * * 23 | * One of the big challenges of scaling an application is that these sorts * 24 | * of clustered deployments cannot share memory, since they are on * 25 | * physically different machines. On top of that, there is no guarantee * 26 | * that a user will "stick" with the same server between requests (whether * 27 | * HTTP or sockets), since the load balancer will route each request to the * 28 | * Sails server with the most available resources. However that means that * 29 | * all room/pubsub/socket processing and shared memory has to be offloaded * 30 | * to a shared, remote messaging queue (usually Redis) * 31 | * * 32 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 33 | * sockets by default. To enable a remote redis pubsub server, uncomment * 34 | * the config below. * 35 | * * 36 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 37 | * is left unset, Sails will try to connect to redis running on localhost * 38 | * via port 6379 * 39 | * * 40 | ***************************************************************************/ 41 | // adapter: 'memory', 42 | 43 | // 44 | // -OR- 45 | // 46 | 47 | // adapter: 'socket.io-redis', 48 | // host: '127.0.0.1', 49 | // port: 6379, 50 | // db: 0, 51 | // pass: '', 52 | 53 | 54 | 55 | /*************************************************************************** 56 | * * 57 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 58 | * a cookie (this is used by the sails.io.js socket client to get access to * 59 | * a 3rd party cookie and to enable sessions). * 60 | * * 61 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 62 | * requests sent via a socket.io connection that used this cookie to * 63 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 64 | * unit tests) * 65 | * * 66 | ***************************************************************************/ 67 | 68 | // grant3rdPartyCookie: true, 69 | 70 | 71 | 72 | /*************************************************************************** 73 | * * 74 | * `beforeConnect` * 75 | * * 76 | * This custom beforeConnect function will be run each time BEFORE a new * 77 | * socket is allowed to connect, when the initial socket.io handshake is * 78 | * performed with the server. * 79 | * * 80 | * By default, when a socket tries to connect, Sails allows it, every time. * 81 | * (much in the same way any HTTP request is allowed to reach your routes. * 82 | * If no valid cookie was sent, a temporary session will be created for the * 83 | * connecting socket. * 84 | * * 85 | * If the cookie sent as part of the connection request doesn't match any * 86 | * known user session, a new user session is created for it. * 87 | * * 88 | * In most cases, the user would already have a cookie since they loaded * 89 | * the socket.io client and the initial HTML page you're building. * 90 | * * 91 | * However, in the case of cross-domain requests, it is possible to receive * 92 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 93 | * In this case, there is no way to keep track of the requesting user * 94 | * between requests, since there is no identifying information to link * 95 | * him/her with a session. The sails.io.js client solves this by connecting * 96 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 97 | * works, even in Safari), then opening the connection. * 98 | * * 99 | * You can also pass along a ?cookie query parameter to the upgrade url, * 100 | * which Sails will use in the absence of a proper cookie e.g. (when * 101 | * connecting from the client): * 102 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 103 | * * 104 | * Finally note that the user's cookie is NOT (and will never be) accessible* 105 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 106 | * app's security. * 107 | * * 108 | ***************************************************************************/ 109 | // beforeConnect: function(handshake, cb) { 110 | // // `true` allows the connection 111 | // return cb(null, true); 112 | // 113 | // // (`false` would reject the connection) 114 | // }, 115 | 116 | 117 | /*************************************************************************** 118 | * * 119 | * `afterDisconnect` * 120 | * * 121 | * This custom afterDisconnect function will be run each time a socket * 122 | * disconnects * 123 | * * 124 | ***************************************************************************/ 125 | // afterDisconnect: function(session, socket, cb) { 126 | // // By default: do nothing. 127 | // return cb(); 128 | // }, 129 | 130 | /*************************************************************************** 131 | * * 132 | * `transports` * 133 | * * 134 | * A array of allowed transport methods which the clients will try to use. * 135 | * On server environments that don't support sticky sessions, the "polling" * 136 | * transport should be disabled. * 137 | * * 138 | ***************************************************************************/ 139 | // transports: ["polling", "websocket"] 140 | 141 | }; 142 | -------------------------------------------------------------------------------- /server/config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * View Engine Configuration 3 | * (sails.config.views) 4 | * 5 | * Server-sent views are a classic and effective way to get your app up 6 | * and running. Views are normally served from controllers. Below, you can 7 | * configure your templating language/framework of choice and configure 8 | * Sails' layout support. 9 | * 10 | * For more information on views and layouts, check out: 11 | * http://sailsjs.org/#!/documentation/concepts/Views 12 | */ 13 | 14 | module.exports.views = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * View engine (aka template language) to use for your app's *server-side* * 19 | * views * 20 | * * 21 | * Sails+Express supports all view engines which implement TJ Holowaychuk's * 22 | * `consolidate.js`, including, but not limited to: * 23 | * * 24 | * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, * 25 | * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, * 26 | * toffee, walrus, & whiskers * 27 | * * 28 | * For more options, check out the docs: * 29 | * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine * 30 | * * 31 | ****************************************************************************/ 32 | 33 | engine: 'ejs', 34 | 35 | 36 | /**************************************************************************** 37 | * * 38 | * Layouts are simply top-level HTML templates you can use as wrappers for * 39 | * your server-side views. If you're using ejs or jade, you can take * 40 | * advantage of Sails' built-in `layout` support. * 41 | * * 42 | * When using a layout, when one of your views is served, it is injected * 43 | * into the `body` partial defined in the layout. This lets you reuse header * 44 | * and footer logic between views. * 45 | * * 46 | * NOTE: Layout support is only implemented for the `ejs` view engine! * 47 | * For most other engines, it is not necessary, since they implement * 48 | * partials/layouts themselves. In those cases, this config will be * 49 | * silently ignored. * 50 | * * 51 | * The `layout` setting may be set to one of the following: * 52 | * * 53 | * If `false`, layouts will be disabled. Otherwise, if a string is * 54 | * specified, it will be interpreted as the relative path to your layout * 55 | * file from `views/` folder. (the file extension, ".ejs", should be * 56 | * omitted) * 57 | * * 58 | ****************************************************************************/ 59 | 60 | /**************************************************************************** 61 | * * 62 | * Using Multiple Layouts * 63 | * * 64 | * If you're using the default `ejs` or `handlebars` Sails supports the use * 65 | * of multiple `layout` files. To take advantage of this, before rendering a * 66 | * view, override the `layout` local in your controller by setting * 67 | * `res.locals.layout`. (this is handy if you parts of your app's UI look * 68 | * completely different from each other) * 69 | * * 70 | * e.g. your default might be * 71 | * layout: 'layouts/public' * 72 | * * 73 | * But you might override that in some of your controllers with: * 74 | * layout: 'layouts/internal' * 75 | * * 76 | ****************************************************************************/ 77 | 78 | layout: 'layout', 79 | 80 | /**************************************************************************** 81 | * * 82 | * Partials are simply top-level snippets you can leverage to reuse template * 83 | * for your server-side views. If you're using handlebars, you can take * 84 | * advantage of Sails' built-in `partials` support. * 85 | * * 86 | * If `false` or empty partials will be located in the same folder as views. * 87 | * Otherwise, if a string is specified, it will be interpreted as the * 88 | * relative path to your partial files from `views/` folder. * 89 | * * 90 | ****************************************************************************/ 91 | 92 | partials: false 93 | 94 | 95 | }; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-authentication-server", 3 | "version": "1.0.0", 4 | "description": "Demo server for react-native-authentication", 5 | "keywords": [], 6 | "dependencies": { 7 | "bcrypt": "^0.8.7", 8 | "ejs": "^2.5.2", 9 | "eslint-config-airbnb-base": "^9.0.0", 10 | "include-all": "^1.0.0", 11 | "moment": "^2.15.2", 12 | "passport": "^0.3.2", 13 | "passport-http": "^0.3.0", 14 | "passport-http-bearer": "^1.0.1", 15 | "rand-token": "^0.2.1", 16 | "rc": "1.0.1", 17 | "sails": "~0.12.9", 18 | "sails-disk": "~0.10.9", 19 | "sails-hook-validation": "^0.4.6" 20 | }, 21 | "scripts": { 22 | "start": "babel-node app.js" 23 | }, 24 | "main": "app.js", 25 | "author": "Alexis Mangin", 26 | "license": "MIT", 27 | "repository": {}, 28 | "devDependencies": { 29 | "babel-eslint": "^7.1.0", 30 | "eslint": "^3.9.1", 31 | "eslint-plugin-import": "^2.1.0" 32 | } 33 | } 34 | --------------------------------------------------------------------------------