├── 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 | *
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 |
--------------------------------------------------------------------------------