Add OAuth Login facebook,google,github

This commit is contained in:
Qolzam
2017-11-18 19:04:31 +07:00
parent f64b7bb24d
commit 92379b615c
23 changed files with 513 additions and 264 deletions

11
.babelrc Normal file
View File

@@ -0,0 +1,11 @@
{
"plugins": [
"react-hot-loader/babel",
"transform-decorators-legacy"
],
"presets": [
"babel-polyfill", ["env", { "modules": false }],
"react",
"stage-0"
]
}

View File

@@ -13,6 +13,7 @@
"author": "Amir Movahedi",
"license": "MIT",
"dependencies": {
"@types/react-hot-loader": "^3.0.5",
"amazon-cognito-identity-js": "^1.21.0",
"aws-sdk": "^2.132.0",
"axios": "^0.16.1",
@@ -39,6 +40,7 @@
"react-avatar-editor": "^10.3.0",
"react-dom": "^16.0.0",
"react-event-listener": "^0.5.1",
"react-hot-loader": "^3.1.3",
"react-linkify": "^0.2.1",
"react-parallax": "^1.4.4",
"react-redux": "^5.0.6",
@@ -97,7 +99,8 @@
"tslint": "^5.7.0",
"tslint-config-standard": "^6.0.1",
"typescript": "^2.5.3",
"webpack": "^3.6.0"
"webpack": "^3.6.0",
"webpack-hot-middleware": "^2.20.0"
},
"engines": {
"node": "7.3.0",

View File

@@ -1,6 +1,9 @@
var express = require('express');
var morgan = require('morgan');
var path = require('path');
var webpack = require('webpack');
var webpackConfig = require('./webpack.config');
var compiler = webpack(webpackConfig);
// Create our app
var app = express();
@@ -10,7 +13,14 @@ const PORT = process.env.PORT || 3000;
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));
app.use(function (req, res, next){
app.use(require("webpack-dev-middleware")(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath
}));
app.use(require("webpack-hot-middleware")(compiler));
app.use(function(req, res, next) {
if (req.headers['x-forwarded-proto'] === 'https') {
res.redirect('http://' + req.hostname + req.url);
} else {
@@ -34,6 +44,6 @@ app.get('*', (req, res) => {
});
app.listen(PORT, function () {
app.listen(PORT, function() {
console.log('Express server is up on port ' + PORT);
});

View File

@@ -6,6 +6,7 @@ import { push } from 'react-router-redux'
// -Import domain
import { User } from 'core/domain/users'
import { SocialError } from 'core/domain/common'
import { OAuthType, LoginUser } from 'core/domain/authorize'
import { UserRegisterModel } from 'models/users/userRegisterModel'
@@ -150,6 +151,27 @@ export const dbSendEmailVerfication = () => {
}
}
/**
* Login user with OAuth
*/
export const dbLoginWithOAuth = (type: OAuthType) => {
return (dispatch: any, getState: any) => {
dispatch(globalActions.showNotificationRequest())
return authorizeService.loginWithOAuth(type).then((result: LoginUser) => {
// Send email verification successful.
dispatch(globalActions.showNotificationSuccess())
dispatch(login(result.uid, true))
dispatch(push('/'))
})
.catch((error: SocialError) => {
// An error happened.
dispatch(globalActions.showErrorMessage(error.code))
})
}
}
/* _____________ CRUD State _____________ */
/**

View File

@@ -27,9 +27,13 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
message: {
fontWeight: 100
},
sendButton: {
buttons: {
marginTop: 60
},
homeButton: {
marginRight: 10
}
}
/**
@@ -89,9 +93,12 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
<p style={this.styles.message as any}>
An verificiation email has been already sent to you. Please check your inbox. If you couldn't see the emai, please resend email verification.
</p>
<div style={this.styles.sendButton}>
<div style={this.styles.buttons}>
<RaisedButton style={this.styles.homeButton} label='Home' primary={true} onClick={() => this.props.homePage()} />
<RaisedButton label='Send Email Verification' primary={true} onClick={() => this.props.sendEmailVerification()} />
</div>
<div>
</div>
</div>
</Paper>
@@ -109,8 +116,8 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
*/
const mapDispatchToProps = (dispatch: Function, ownProps: IEmailVerificationComponentProps) => {
return {
loginPage: () => {
dispatch(push('/login'))
homePage: () => {
dispatch(push('/'))
},
sendEmailVerification: () => dispatch(authorizeActions.dbSendEmailVerfication())
}

View File

@@ -1,4 +1,5 @@
export interface IEmailVerificationComponentProps {
sendEmailVerification: () => any
homePage: () => any
}

View File

@@ -111,7 +111,8 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
goTo!('/login')
return
}
if (!isVerifide) {
// if (!isVerifide) {
if (false) {
goTo!('/emailVerification')
} else if (!global.defaultLoadDataStatus) {

View File

@@ -1,3 +1,4 @@
import { OAuthType } from 'core/domain/authorize'
export interface ILoginComponentProps {
/**
@@ -7,6 +8,13 @@ export interface ILoginComponentProps {
*/
login?: (email: string , password: string) => any
/**
* Login user with OAuth
*
* @memberof ILoginComponentProps
*/
loginWithOAuth: (type: OAuthType) => any
/**
* Redirect to signup page
*

View File

@@ -15,6 +15,8 @@ import ActionAndroid from 'material-ui/svg-icons/action/android'
import * as authorizeActions from 'actions/authorizeActions'
import { ILoginComponentProps } from './ILoginComponentProps'
import { ILoginComponentState } from './ILoginComponentState'
import { firebaseAuth } from 'data/firebaseClient'
import { OAuthType } from 'core/domain/authorize'
// - Create Login component class
export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
@@ -130,6 +132,8 @@ export class LoginComponent extends Component<ILoginComponentProps,ILoginCompone
display: 'block',
margin: 'auto'
}
const {loginWithOAuth} = this.props
return (
<form>
@@ -163,12 +167,16 @@ export class LoginComponent extends Component<ILoginComponentProps,ILoginCompone
<div style={this.styles.singinOptions}>
<FlatButton
icon={<div className='icon-fb icon'></div>}
onClick={() => loginWithOAuth(OAuthType.FACEBOOK)}
/>
<FlatButton
icon={<div className='icon-google icon'></div>}
onClick={() => loginWithOAuth(OAuthType.GOOGLE)}
/>
<FlatButton
icon={<div className='icon-github icon'></div>}
onClick={() => loginWithOAuth(OAuthType.GITHUB)}
/>
</div>
@@ -221,6 +229,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
login: (email: string, password: string) => {
dispatch(authorizeActions.dbLogin(email, password))
},
loginWithOAuth: (type: OAuthType) => dispatch(authorizeActions.dbLoginWithOAuth(type)),
signupPage: () => {
dispatch(push('/signup'))
}

View File

@@ -77,7 +77,6 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
componentDidCatch (error: any, info: any) {
console.log('===========Catched by React componentDidCatch==============')
console.log(error, info)
alert({error, info})
console.log('====================================')
}

View File

@@ -1,7 +1,9 @@
import { LoginUser } from './loginResult'
import { OAuthType } from './oauthType'
import { LoginUser } from './loginUser'
import { RegisterUserResult } from './registerUserResult'
export {
LoginUser,
RegisterUserResult
RegisterUserResult,
OAuthType
}

View File

@@ -1,23 +0,0 @@
import { BaseDomain } from 'core/domain/common'
export class LoginUser extends BaseDomain {
constructor (private _uid: string, private _emailVerified: boolean) {
super()
}
/**
* User identifier
*
* @type {string}
* @memberof LoginUser
*/
public get uid (): string {
return this._uid
}
public get emailVerified (): boolean {
return this._emailVerified
}
}

View File

@@ -0,0 +1,54 @@
import { BaseDomain } from 'core/domain/common'
export class LoginUser extends BaseDomain {
constructor (
private _uid: string,
private _emailVerified: boolean,
private _providerId: string = '',
private _displayName: string = '',
private _email: string = '',
private _avatarURL: string = ''
) {
super()
}
/**
* User identifier
*
* @type {string}
* @memberof LoginUser
*/
public get uid (): string {
return this._uid
}
/**
* If user's email is verifide {true} or not {false}
*
* @readonly
* @type {boolean}
* @memberof LoginUser
*/
public get emailVerified (): boolean {
return this._emailVerified
}
public get providerId (): string {
return this._providerId
}
public get displayName (): string {
return this._displayName
}
public get email (): string {
return this.email
}
public get avatarURL (): string {
return this._avatarURL
}
}

View File

@@ -0,0 +1,5 @@
export enum OAuthType {
GITHUB = 'GITHUB',
FACEBOOK = 'FACEBOOK',
GOOGLE = 'GOOGLE'
}

View File

@@ -1,8 +1,9 @@
import { BaseDomain } from 'core/domain/common'
export class RegisterUserResult extends BaseDomain{
export class RegisterUserResult extends BaseDomain {
constructor(uid: string){
private _uid: string
constructor (uid: string) {
super()
this._uid = uid
@@ -14,10 +15,7 @@ export class RegisterUserResult extends BaseDomain{
* @memberof LoginUser
*/
private _uid : string
public get uid (): string {
return this._uid
}
}

View File

@@ -1,7 +1,9 @@
import {User} from './user'
import {Profile} from './profile'
import { User } from './user'
import { Profile } from './profile'
import { UserProvider } from './userProvider'
export {
User,
Profile
Profile,
UserProvider
}

View File

@@ -1,45 +1,14 @@
import { BaseDomain } from 'core/domain/common'
export class Profile extends BaseDomain {
constructor (
public avatar: string,
public fullName: string,
public banner: string,
public tagLine: string,
public email?: string | null) {
super()
/**
* User avatar address
*
* @type {string}
* @memberof Profile
*/
public avatar: string
/**
* User email
*
* @type {string}
* @memberof Profile
*/
public email?: string | null
/**
* User full name
*
* @type {string}
* @memberof Profile
*/
public fullName: string
/**
* The banner address of user profile
*
* @type {string}
* @memberof Profile
*/
public banner: string
/**
* User tag line
*
* @type {string}
* @memberof Profile
*/
public tagLine: string
}
}

View File

@@ -0,0 +1,18 @@
/**
* User provide data
*
* @export
* @class UserProvider
*/
export class UserProvider {
constructor (
public userId: string,
public email: string,
public fullName: string,
public avatar: string,
public providerId: string,
public provider: string,
public accessToken: string
) {}
}

View File

@@ -1,5 +1,5 @@
import { User } from 'core/domain/users'
import { LoginUser, RegisterUserResult } from 'core/domain/authorize'
import { LoginUser, RegisterUserResult, OAuthType } from 'core/domain/authorize'
/**
* Authentication service interface
@@ -55,4 +55,11 @@ export interface IAuthorizeService {
* @memberof IAuthorizeService
*/
sendEmailVerification: () => Promise<void>
/**
* Login user by OAuth authentication
*
* @memberof IAuthorizeService
*/
loginWithOAuth: (type: OAuthType) => Promise<LoginUser>
}

View File

@@ -1,11 +1,13 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { IAuthorizeService } from 'core/services/authorize'
import { User } from 'core/domain/users'
import { User, UserProvider } from 'core/domain/users'
import { LoginUser, RegisterUserResult } from 'core/domain/authorize'
import { SocialError } from 'core/domain/common'
import { OAuthType } from 'core/domain/authorize/oauthType'
/**
* Firbase authorize service
*
@@ -66,15 +68,8 @@ export class AuthorizeService implements IAuthorizeService {
firebaseAuth()
.createUserWithEmailAndPassword(user.email as string, user.password as string)
.then((signupResult) => {
firebaseRef.child(`users/${signupResult.uid}/info`)
.set({
...user,
avatar: 'noImage'
})
.then((result) => {
resolve(new RegisterUserResult(signupResult.uid))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
const {uid, email, displayName, photoURL} = signupResult
this.storeUserInformation(uid,email,displayName,photoURL).then(resolve)
})
.catch((error: any) => reject(new SocialError(error.code, error.message)))
})
@@ -140,6 +135,11 @@ export class AuthorizeService implements IAuthorizeService {
})
}
/**
* Send verfication email to user email
*
* @memberof AuthorizeService
*/
public sendEmailVerification: () => Promise<void> = () => {
return new Promise<void>((resolve,reject) => {
let auth = firebaseAuth()
@@ -153,9 +153,109 @@ export class AuthorizeService implements IAuthorizeService {
reject(new SocialError(error.code, error.message))
})
} else {
reject(new SocialError('nullException', 'User was null'));
reject(new SocialError('authorizeService/nullException', 'User was null!'))
}
})
}
public loginWithOAuth: (type: OAuthType) => Promise<LoginUser> = (type) => {
return new Promise<LoginUser>((resolve,reject) => {
let provider: any
switch (type) {
case OAuthType.GITHUB:
provider = new firebaseAuth.GithubAuthProvider()
break
case OAuthType.FACEBOOK:
provider = new firebaseAuth.FacebookAuthProvider()
break
case OAuthType.GOOGLE:
provider = new firebaseAuth.GoogleAuthProvider()
break
default:
throw new SocialError('authorizeService/loginWithOAuth','None of OAuth type is matched!')
}
firebaseAuth().signInWithPopup(provider).then((result) => {
// This gives you a GitHub Access Token. You can use it to access the GitHub API.
let token = result.credential.accessToken
// The signed-in user info.
const {user} = result
const {credential} = result
const {uid, displayName, email, photoURL} = user
const {accessToken, provider, providerId} = credential
this.storeUserProviderData(uid,email,displayName,photoURL,providerId,provider,accessToken)
// this.storeUserInformation(uid,email,displayName,photoURL).then(resolve)
resolve(new LoginUser(user.uid,true,providerId,displayName,email,photoURL))
}).catch(function (error: any) {
// Handle Errors here.
let errorCode = error.code
let errorMessage = error.message
// The email of the user's account used.
let email = error.email
// The firebase.auth.AuthCredential type that was used.
let credential = error.credential
})
})
}
/**
* Store user information
*
* @private
* @memberof AuthorizeService
*/
private storeUserInformation = (userId: string, email: string, fullName: string, avatar?: string) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
firebaseRef.child(`users/${userId}/info`)
.set({
userId,
avatar,
email,
fullName
})
.then((result) => {
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
})
}
/**
* Store user provider information
*
* @private
* @memberof AuthorizeService
*/
private storeUserProviderData = (
userId: string,
email: string,
fullName: string,
avatar: string,
providerId: string,
provider: string,
accessToken: string
) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
firebaseRef.child(`users/${userId}/providerInfo`)
.set(new UserProvider(
userId,
email,
fullName,
avatar,
providerId,
provider,
accessToken
))
.then((result) => {
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
})
}
}

View File

@@ -2,7 +2,7 @@
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { Profile } from 'core/domain/users'
import { Profile, UserProvider } from 'core/domain/users'
import { IUserService } from 'core/services/users'
/**
@@ -15,22 +15,32 @@ import { IUserService } from 'core/services/users'
export class UserService implements IUserService {
public getUserProfile: (userId: string)
=> Promise<Profile> = (userId) => {
return new Promise<Profile>((resolve,reject) => {
return new Promise<Profile>((resolve, reject) => {
let userProfileRef: any = firebaseRef.child(`users/${userId}/info`)
userProfileRef.once('value').then((snapshot: any) => {
let userProfile: Profile = snapshot.val() || {}
if (Object.keys(userProfile).length === 0 && userProfile.constructor === Object) {
this.getUserProviderData(userId).then((providerData: UserProvider) => {
const {avatar,fullName, email} = providerData
const userProfile = new Profile(avatar,fullName,'','',email)
resolve(userProfile)
this.updateUserProfile(userId,userProfile)
})
} else {
resolve(userProfile)
}
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
reject(new SocialError(error.code, error.message))
})
})
}
public updateUserProfile: (userId: string, profile: Profile)
=> Promise<void> = (userId, profile) => {
return new Promise<void>((resolve,reject) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`users/${userId}/info`] = profile
@@ -38,18 +48,18 @@ export class UserService implements IUserService {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
reject(new SocialError(error.code, error.message))
})
})
}
public getUsersProfile: (userId: string)
=> Promise<{ [userId: string]: Profile }> = (userId) => {
return new Promise<{ [userId: string]: Profile }>((resolve,reject) => {
return new Promise<{ [userId: string]: Profile }>((resolve, reject) => {
let usersProfileRef: any = firebaseRef.child(`users`)
usersProfileRef.once('value').then((snapshot: any) => {
let usersProfile: any = snapshot.val() || {}
let parsedusersProfile: {[userId: string]: Profile} = {}
let parsedusersProfile: { [userId: string]: Profile } = {}
Object.keys(usersProfile).forEach((userKey) => {
if (userId !== userKey) {
let userInfo = usersProfile[userKey].info
@@ -65,9 +75,27 @@ export class UserService implements IUserService {
resolve(parsedusersProfile)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
reject(new SocialError(error.code, error.message))
})
})
}
private getUserProviderData = (userId: string) => {
return new Promise<UserProvider>((resolve,reject) => {
let userProviderRef: any = firebaseRef.child(`users/${userId}/providerInfo`)
userProviderRef.once('value').then((snapshot: any) => {
let userProviderRef: any = firebaseRef.child(`users/${userId}/info`)
let userProvider: UserProvider = snapshot.val() || {}
console.log('----------------userProfile')
console.log(userProvider)
console.log('-----------------------')
resolve(userProvider)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,6 +1,7 @@
// Import external components refrence
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import injectTapEventPlugin from 'react-tap-event-plugin'
import { cyan500 } from 'material-ui/styles/colors'
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
@@ -20,7 +21,7 @@ import Master from 'components/master'
// Set default data
// tslint:disable-next-line:no-empty
store.subscribe(() => {})
store.subscribe(() => { })
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
@@ -37,14 +38,35 @@ const muiTheme = getMuiTheme({
import 'applicationStyles'
const supportsHistory = 'pushState' in window.history
ReactDOM.render(
// ReactDOM.render(
// <Provider store={store}>
// <ConnectedRouter history={history}>
// <MuiThemeProvider muiTheme={getMuiTheme(lightBaseTheme)}>
// <Master />
// </MuiThemeProvider>
// </ConnectedRouter>
// </Provider>,
// document.getElementById('app')
// )
const render = (Component: any) => {
ReactDOM.render(
<AppContainer warnings={false}>
<Provider store={store}>
<ConnectedRouter history={history}>
<MuiThemeProvider muiTheme={getMuiTheme(lightBaseTheme)}>
<Master />
{/* <App /> */}
<Component />
</MuiThemeProvider>
</ConnectedRouter>
</Provider>,
</Provider>
</AppContainer>,
document.getElementById('app')
)
)
}
render(Master)
// Webpack Hot Module Replacement API
if (module.hot) {
module.hot.accept('components/master', () => { render(Master) })
}

View File

@@ -11,14 +11,10 @@ try {
} catch (e) {
}
var babelOptions = {
plugins: ['transform-decorators-legacy'],
presets: ['babel-polyfill', 'react', 'env', 'stage-0']
};
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-hot-middleware/client',
'./src/index.tsx'
],
externals: {
@@ -44,6 +40,7 @@ module.exports = {
}
})
] : [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
@@ -58,6 +55,7 @@ module.exports = {
})
],
output: {
publicPath: '/',
path: path.resolve(__dirname, './public'),
filename: 'bundle-v0.1.js',
@@ -91,8 +89,7 @@ module.exports = {
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: babelOptions
loader: 'babel-loader'
},
{
loader: 'ts-loader',
@@ -109,8 +106,7 @@ module.exports = {
test: /\.js(x?)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: babelOptions
loader: 'babel-loader'
}
},
{