test
parent
c02176ba9a
commit
9f4e9db3f5
4
.sailsrc
4
.sailsrc
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"generators": {
|
"generators": {
|
||||||
"modules": {}
|
"modules": {
|
||||||
|
"permissions-api": "sails-permissions/generator"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"_generatedWith": {
|
"_generatedWith": {
|
||||||
"sails": "1.0.2",
|
"sails": "1.0.2",
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/**
|
||||||
|
* Authentication Controller
|
||||||
|
*/
|
||||||
|
// some also from https://github.com/trailsjs/sails-auth
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if the given email has a corresponding user
|
||||||
|
*/
|
||||||
|
emailExists: async function (req, res) {
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: req.param('email')
|
||||||
|
})
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
error: 'user does not exist'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return res.json({
|
||||||
|
status: 'ok'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* opposite of emailExists
|
||||||
|
*/
|
||||||
|
emailAvailable: async function (req, res) {
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: req.param('email')
|
||||||
|
})
|
||||||
|
if (user) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'that email address is not available'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return res.json({
|
||||||
|
status: 'ok'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log out a user and return them to the homepage
|
||||||
|
*
|
||||||
|
* Passport exposes a logout() function on req (also aliased as logOut()) that
|
||||||
|
* can be called from any route handler which needs to terminate a login
|
||||||
|
* session. Invoking logout() will remove the req.user property and clear the
|
||||||
|
* login session (if any).
|
||||||
|
*
|
||||||
|
* For more information on logging out users in Passport.js, check out:
|
||||||
|
* http://passportjs.org/guide/logout/
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Object} res
|
||||||
|
*/
|
||||||
|
logout: function (req, res) {
|
||||||
|
req.logout()
|
||||||
|
delete req.user
|
||||||
|
delete req.session.passport
|
||||||
|
req.session.authenticated = false
|
||||||
|
|
||||||
|
if (!req.isSocket) {
|
||||||
|
res.redirect(req.query.next || '/')
|
||||||
|
} else {
|
||||||
|
res.ok()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a third-party authentication endpoint
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Object} res
|
||||||
|
*/
|
||||||
|
provider: async function (req, res) {
|
||||||
|
const passportHelper = await sails.helpers.passport()
|
||||||
|
passportHelper.endpoint(req, res)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a authentication callback endpoint
|
||||||
|
*
|
||||||
|
* This endpoint handles everything related to creating and verifying Pass-
|
||||||
|
* ports and users, both locally and from third-aprty providers.
|
||||||
|
*
|
||||||
|
* Passport exposes a login() function on req that
|
||||||
|
* can be used to establish a login session. When the login operation
|
||||||
|
* completes, user will be assigned to req.user.
|
||||||
|
*
|
||||||
|
* For more information on logging in users in Passport.js, check out:
|
||||||
|
* http://passportjs.org/guide/login/
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Object} res
|
||||||
|
*/
|
||||||
|
callback: async function (req, res) {
|
||||||
|
const action = req.param('action')
|
||||||
|
const passportHelper = await sails.helpers.passport()
|
||||||
|
|
||||||
|
function negotiateError (err) {
|
||||||
|
if (action === 'register') {
|
||||||
|
res.redirect('/register')
|
||||||
|
} else if (action === 'login') {
|
||||||
|
res.redirect('/login')
|
||||||
|
} else if (action === 'disconnect') {
|
||||||
|
res.redirect('back')
|
||||||
|
} else {
|
||||||
|
// make sure the server always returns a response to the client
|
||||||
|
// i.e passport-local bad username/email or password
|
||||||
|
res.status(401).json({
|
||||||
|
'error': err.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passportHelper.callback(req, res, function (err, user, info, status) {
|
||||||
|
if (err || !user) {
|
||||||
|
sails.log.warn(user, err, info, status)
|
||||||
|
if (!err && info) {
|
||||||
|
return negotiateError(info)
|
||||||
|
}
|
||||||
|
return negotiateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.login(user, function (err) {
|
||||||
|
if (err) {
|
||||||
|
sails.log.warn(err)
|
||||||
|
return negotiateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.session.authenticated = true
|
||||||
|
|
||||||
|
// redirect if there is a 'next' param
|
||||||
|
if (req.query.next) {
|
||||||
|
res.status(302).set('Location', req.query.next)
|
||||||
|
}
|
||||||
|
|
||||||
|
sails.log.info('user', user, 'authenticated successfully')
|
||||||
|
return res.json(user)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect a passport from a user
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Object} res
|
||||||
|
*/
|
||||||
|
disconnect: async function (req, res) {
|
||||||
|
const passportHelper = await sails.helpers.passport()
|
||||||
|
passportHelper.disconnect(req, res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
show: function (req, res) {
|
||||||
|
res.view('pages/temp', {
|
||||||
|
email: req.user.email
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* UserController
|
||||||
|
*
|
||||||
|
* @description :: Server-side logic for managing Users
|
||||||
|
* @help :: See http://links.sailsjs.org/docs/controllers
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
create: async function (req, res, next) {
|
||||||
|
const passportHelper = await sails.helpers.passport()
|
||||||
|
passportHelper.protocols.local.register(req.body, function (err, user) {
|
||||||
|
if (err) return res.status(500).json({
|
||||||
|
error: err.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json(user)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async function (req, res, next) {
|
||||||
|
const passportHelper = await sails.helpers.passport()
|
||||||
|
passportHelper.protocols.local.update(req.body, function (err, user) {
|
||||||
|
if (err) return res.status(500).json({
|
||||||
|
error: err.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json(user)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
me: function (req, res) {
|
||||||
|
res.json(req.user)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// api/helpers/passport.js
|
||||||
|
// from https://github.com/trailsjs/sails-auth/blob/master/api/services/passport.js
|
||||||
|
|
||||||
|
const url = require('url')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
friendlyName: 'Load PassportHelper',
|
||||||
|
description: 'Load a PassportHelper instance',
|
||||||
|
inputs: {},
|
||||||
|
exits: {
|
||||||
|
success: {
|
||||||
|
outputFriendlyName: 'Passport helper',
|
||||||
|
outputDescription: 'A PassportHelper instance'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fn: async function (inputs, exits) {
|
||||||
|
return exits.success(new PassportHelper())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const passport = require('passport')
|
||||||
|
passport.serializeUser(function (user, next) {
|
||||||
|
next(null, user.id)
|
||||||
|
})
|
||||||
|
passport.deserializeUser(function (id, next) {
|
||||||
|
return User.findOne({id: id})
|
||||||
|
.then(function (user) {
|
||||||
|
next(null, user)
|
||||||
|
return user
|
||||||
|
}).catch(next)
|
||||||
|
})
|
||||||
|
|
||||||
|
function PassportHelper () {
|
||||||
|
this.protocols = sails.config.protocols
|
||||||
|
|
||||||
|
this.loadStrategies = function () {
|
||||||
|
const strategies = sails.config.passport
|
||||||
|
|
||||||
|
for (const key in strategies) {
|
||||||
|
let options = {passReqToCallback: true}
|
||||||
|
let Strategy = strategies[key].strategy
|
||||||
|
if (key === 'local') {
|
||||||
|
_.extend(options, {
|
||||||
|
usernameField: 'identifier'
|
||||||
|
})
|
||||||
|
passport.use(new Strategy(options, this.protocols.local.login))
|
||||||
|
} else {
|
||||||
|
const protocol = strategies[key].protocol
|
||||||
|
const callbackURL = strategies[key].callback
|
||||||
|
let baseURL = ''
|
||||||
|
if (sails.config.appUrl && sails.config.appUrl !== null) {
|
||||||
|
baseURL = sails.config.appUrl
|
||||||
|
} else {
|
||||||
|
sails.log.warn('Please add \'appUrl\' to configuration')
|
||||||
|
baseURL = sails.getBaseurl()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (protocol) {
|
||||||
|
case 'oauth2':
|
||||||
|
options.callbackURL = url.resolve(baseURL, callbackURL)
|
||||||
|
break
|
||||||
|
// other protocols (openid, etc can go here)
|
||||||
|
}
|
||||||
|
|
||||||
|
_.extend(options, strategies[key].options)
|
||||||
|
|
||||||
|
passport.use(new Strategy(options, this.protocols[protocol].login))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.endpoint = function (req, res) {
|
||||||
|
const strategies = sails.config.passport
|
||||||
|
const provider = req.param('provider')
|
||||||
|
|
||||||
|
if (!_.has(strategies, provider)) return res.redirect('/login')
|
||||||
|
|
||||||
|
passport.authenticate(provider, {})(req, res, req.next)
|
||||||
|
}
|
||||||
|
// a callback helper to split by req
|
||||||
|
this.callback = function (req, res, next) {
|
||||||
|
var provider = req.param('provider', 'local')
|
||||||
|
var action = req.param('action')
|
||||||
|
|
||||||
|
if (provider === 'local' && action !== undefined) {
|
||||||
|
if (action === 'register' && !req.user) {
|
||||||
|
this.protocols.local.register(req, res, next)
|
||||||
|
} else if (action === 'connect' && req.user) {
|
||||||
|
this.protocols.local.connect(req, res, next)
|
||||||
|
} else if (action === 'disconnect' && req.user) {
|
||||||
|
this.protocols.local.disconnect(req, res, next)
|
||||||
|
} else {
|
||||||
|
next(new Error('Invalid action'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (action === 'disconnect' && req.user) {
|
||||||
|
this.disconnect(req, res, next)
|
||||||
|
} else {
|
||||||
|
passport.authenticate(provider, next)(req, res, req.next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.connect = async function (req, q, profile, next) {
|
||||||
|
let userAttrs = {}
|
||||||
|
let provider = profile.provider || req.param('provider')
|
||||||
|
|
||||||
|
req.session.tokens = q.tokens
|
||||||
|
q.provider = provider
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return next(new Error('No provider identified'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the profile object from passport has an email, use it
|
||||||
|
if (profile.emails && profile.emails[0]) userAttrs.email = profile.emails[0].value
|
||||||
|
if (!userAttrs.email) return next(new Error('No email available'))
|
||||||
|
|
||||||
|
const pass = await Passport.findOne({
|
||||||
|
provider,
|
||||||
|
identifier: q.identifier.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
let user
|
||||||
|
|
||||||
|
if (!req.user) {
|
||||||
|
if (!passport) { // new user signing up, create a new user
|
||||||
|
user = await User.create(userAttrs).fetch()
|
||||||
|
await Passport.create({
|
||||||
|
...q,
|
||||||
|
user: user.id
|
||||||
|
})
|
||||||
|
next(null, user)
|
||||||
|
} else { // existing user logging in
|
||||||
|
if (_.has(q, 'tokens') && q.tokens !== passport.tokens) {
|
||||||
|
passport.tokens = q.tokens
|
||||||
|
}
|
||||||
|
await passport.save()
|
||||||
|
user = User.findOne(passport.user)
|
||||||
|
next(null, user)
|
||||||
|
}
|
||||||
|
} else { // user logged in and trying to add new Passport
|
||||||
|
if (!passport) {
|
||||||
|
await Passport.create({
|
||||||
|
...q,
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
|
next(null, req.user)
|
||||||
|
} else { // no action, user already logged in and passport exists
|
||||||
|
next(null, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.disconnect = async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const user = req.user
|
||||||
|
const provider = req.param('provider')
|
||||||
|
|
||||||
|
const pass = Passport.findOne({
|
||||||
|
provider,
|
||||||
|
user: user.id
|
||||||
|
})
|
||||||
|
await Passport.destroy(pass.id)
|
||||||
|
next(null, user)
|
||||||
|
return user
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.getPassport = function () {
|
||||||
|
return passport
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
let passportHook = sails.hooks.passport
|
||||||
|
|
||||||
|
if (!passportHook) {
|
||||||
|
passportHook = function (sails) {
|
||||||
|
return {
|
||||||
|
initialize: async function (cb) {
|
||||||
|
const helper = await sails.helpers.passport()
|
||||||
|
helper.loadStrategies()
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = passportHook
|
|
@ -12,6 +12,12 @@ module.exports = {
|
||||||
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
||||||
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
||||||
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
unique: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
columnName: '_id'
|
||||||
|
},
|
||||||
title: {type: 'string', required: true},
|
title: {type: 'string', required: true},
|
||||||
author: {type: 'string'},
|
author: {type: 'string'},
|
||||||
isbn: {type: 'string'},
|
isbn: {type: 'string'},
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
const bcrypt = require('bcrypt')
|
||||||
|
|
||||||
|
async function hashPassword (passport) {
|
||||||
|
try {
|
||||||
|
var config = sails.config.auth.bcrypt
|
||||||
|
var salt = config.rounds
|
||||||
|
if (passport.password) {
|
||||||
|
const hash = await bcrypt.hash(passport.password, salt)
|
||||||
|
passport.password = hash
|
||||||
|
}
|
||||||
|
return passport
|
||||||
|
} catch (e) {
|
||||||
|
delete passport.password
|
||||||
|
sails.log.error(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passport.js
|
||||||
|
*
|
||||||
|
* @description :: A model definition. Represents a database table/collection/etc.
|
||||||
|
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
attributes: {
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
unique: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
columnName: '_id'
|
||||||
|
},
|
||||||
|
// local, oauth2, etc
|
||||||
|
protocol: {
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
password: 'string',
|
||||||
|
accessToken: 'string',
|
||||||
|
provider: 'string',
|
||||||
|
identifier: 'string',
|
||||||
|
tokens: 'json',
|
||||||
|
|
||||||
|
// User association
|
||||||
|
user: {
|
||||||
|
model: 'User',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback run before creating a Passport
|
||||||
|
*/
|
||||||
|
beforeCreate: async function (passport, next) {
|
||||||
|
await hashPassword(passport)
|
||||||
|
return next()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback run before updating
|
||||||
|
*/
|
||||||
|
beforeUpdate: async function (passport, next) {
|
||||||
|
await hashPassword(passport)
|
||||||
|
return next()
|
||||||
|
},
|
||||||
|
|
||||||
|
// methods
|
||||||
|
validatePassword: async function (password, passport) {
|
||||||
|
return bcrypt.compare(password, passport.password)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* User.js
|
||||||
|
*
|
||||||
|
* @description :: A model definition. Represents a database table/collection/etc.
|
||||||
|
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
attributes: {
|
||||||
|
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
||||||
|
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
||||||
|
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
unique: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
columnName: '_id'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
unique: true,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||||
|
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
||||||
|
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
|
||||||
|
|
||||||
|
// ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||||
|
// ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗
|
||||||
|
// ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* sessionAuth
|
||||||
|
*
|
||||||
|
* @module :: Policy
|
||||||
|
* @description :: Simple policy to allow any authenticated user
|
||||||
|
* @docs :: http://sailsjs.org/#!documentation/policies
|
||||||
|
*/
|
||||||
|
module.exports = function (req, res, next) {
|
||||||
|
if (req.session.authenticated) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
res.status(403).json({ error: 'You are not permitted to perform this action.' })
|
||||||
|
// res.redirect('/login')
|
||||||
|
}
|
|
@ -28,10 +28,13 @@ export const setPassword = pass => ({
|
||||||
data: pass
|
data: pass
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setCarousel = pos => ({
|
export const setCarousel = pos => (dispatch, getState) => {
|
||||||
type: ACTIONS.set_carousel,
|
dispatch(clearError())
|
||||||
data: pos
|
dispatch({
|
||||||
})
|
type: ACTIONS.set_carousel,
|
||||||
|
data: pos
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const setError = data => ({
|
export const setError = data => ({
|
||||||
type: ACTIONS.set_error,
|
type: ACTIONS.set_error,
|
||||||
|
@ -43,22 +46,35 @@ export const clearError = () => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setLoggedIn = (data) => (dispatch, getState) => {
|
export const setLoggedIn = (data) => (dispatch, getState) => {
|
||||||
document.localStorage.setItem('roe-token', JSON.stringify(data))
|
window.localStorage.setItem('roe-token', JSON.stringify(data))
|
||||||
window.location.href = '/app'
|
window.location.href = '/app'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkEmail = email => (dispatch, getState) => {
|
export const checkEmail = email => async (dispatch, getState) => {
|
||||||
// dispatch(setWorking(true))
|
dispatch(setWorking(true))
|
||||||
dispatch(clearError())
|
dispatch(clearError())
|
||||||
if (/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(email)) {
|
if (/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(email)) {
|
||||||
dispatch(setCarousel(2))
|
try {
|
||||||
|
const res = await Ajax.post({
|
||||||
|
url: '/auth/email_exists',
|
||||||
|
data: {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch(setCarousel(2))
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(setError({
|
||||||
|
type: 'email',
|
||||||
|
error: 'An account with that email does not exist.'
|
||||||
|
}))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError({
|
dispatch(setError({
|
||||||
type: 'email',
|
type: 'email',
|
||||||
error: 'Please enter a valid email address.'
|
error: 'Please enter a valid email address.'
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
// dispatch(setWorking(false))
|
dispatch(setWorking(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkPassword = (email, password) => async (dispatch, getState) => {
|
export const checkPassword = (email, password) => async (dispatch, getState) => {
|
||||||
|
@ -67,10 +83,9 @@ export const checkPassword = (email, password) => async (dispatch, getState) =>
|
||||||
// do email + password check
|
// do email + password check
|
||||||
try {
|
try {
|
||||||
const res = await Ajax.post({
|
const res = await Ajax.post({
|
||||||
url: '/api/token',
|
url: '/auth/local',
|
||||||
data: {
|
data: {
|
||||||
grant_type: 'credentials',
|
identifier: email,
|
||||||
email,
|
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -84,3 +99,37 @@ export const checkPassword = (email, password) => async (dispatch, getState) =>
|
||||||
dispatch(setWorking(false))
|
dispatch(setWorking(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const signup = (email, password) => async (dispatch, getState) => {
|
||||||
|
dispatch(setWorking(true))
|
||||||
|
dispatch(clearError())
|
||||||
|
if (/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(email)) {
|
||||||
|
try {
|
||||||
|
await Ajax.post({
|
||||||
|
url: '/auth/email_available',
|
||||||
|
data: {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await Ajax.post({
|
||||||
|
url: '/register',
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch(setCarousel(2))
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(setError({
|
||||||
|
type: 'email',
|
||||||
|
error: e.toString()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(setError({
|
||||||
|
type: 'email',
|
||||||
|
error: 'Please enter a valid email address.'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
dispatch(setWorking(false))
|
||||||
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ export default class Ajax {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(new AjaxError(e.toString(), data, xhr))
|
reject(new AjaxError(e.toString(), data, xhr))
|
||||||
} finally {
|
} finally {
|
||||||
reject(new AjaxError(xhr.status, data, xhr))
|
reject(new AjaxError(data, xhr.status, xhr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Progress from './components/Progress'
|
||||||
import Carousel, {CarouselItem} from './containers/Carousel'
|
import Carousel, {CarouselItem} from './containers/Carousel'
|
||||||
import UnderlineInput from './components/UnderlineInput'
|
import UnderlineInput from './components/UnderlineInput'
|
||||||
import reducer from './reducers/login'
|
import reducer from './reducers/login'
|
||||||
import {setEmail, setPassword, setCarousel, checkEmail, checkPassword} from './actions/login'
|
import {setEmail, setPassword, setCarousel, checkEmail, checkPassword, signup} from './actions/login'
|
||||||
|
|
||||||
import STYLE from '../styles/login.scss'
|
import STYLE from '../styles/login.scss'
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class App extends React.Component {
|
||||||
getPasswordInputs () {
|
getPasswordInputs () {
|
||||||
return [
|
return [
|
||||||
<UnderlineInput
|
<UnderlineInput
|
||||||
key={0}
|
key={1}
|
||||||
type='password'
|
type='password'
|
||||||
name='password'
|
name='password'
|
||||||
placeholder='Password'
|
placeholder='Password'
|
||||||
|
@ -81,10 +81,10 @@ class App extends React.Component {
|
||||||
<Carousel position={this.state.carouselPosition} >
|
<Carousel position={this.state.carouselPosition} >
|
||||||
<CarouselItem
|
<CarouselItem
|
||||||
header='Sign up'
|
header='Sign up'
|
||||||
inputs={this.getEmailInputs()}
|
inputs={this.getEmailInputs().concat(this.getPasswordInputs())}
|
||||||
error={this.state.emailError}
|
error={this.state.emailError}
|
||||||
button='Sign up'
|
button='Sign up'
|
||||||
onButtonClick={() => null}
|
onButtonClick={() => this.dispatch(signup(this.state.user.email, this.state.user.password))}
|
||||||
smallButton='Have an account?'
|
smallButton='Have an account?'
|
||||||
onSmallButtonClick={() => this.dispatch(setCarousel(1))}
|
onSmallButtonClick={() => this.dispatch(setCarousel(1))}
|
||||||
footer='Sign up with your Google account' />
|
footer='Sign up with your Google account' />
|
||||||
|
|
|
@ -22,8 +22,8 @@ $background-2: white;
|
||||||
$text-dark-1: $black-1;
|
$text-dark-1: $black-1;
|
||||||
$text-dark-2: $black-2;
|
$text-dark-2: $black-2;
|
||||||
|
|
||||||
$accent-1: #4423c4;
|
$accent-1: #731212;
|
||||||
$accent-2: #4460c4;
|
$accent-2: #9a834d;
|
||||||
$accent-3: #D4DBF1;
|
$accent-3: #D4DBF1;
|
||||||
|
|
||||||
$red: #FE4C52;
|
$red: #FE4C52;
|
|
@ -84,4 +84,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& + .underlined-input,
|
||||||
|
& + .underlined-input-readonly {
|
||||||
|
margin-top: 8px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>
|
if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>
|
||||||
<link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><%
|
<link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><%
|
||||||
} %>
|
} %>
|
||||||
|
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports.auth = {
|
||||||
|
bcrypt: {
|
||||||
|
rounds: 8
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,8 @@ module.exports.http = {
|
||||||
'rateLimit',
|
'rateLimit',
|
||||||
'cookieParser',
|
'cookieParser',
|
||||||
'session',
|
'session',
|
||||||
|
'passportInit',
|
||||||
|
'passportSession',
|
||||||
'bodyParser',
|
'bodyParser',
|
||||||
'compress',
|
'compress',
|
||||||
'poweredBy',
|
'poweredBy',
|
||||||
|
@ -50,6 +52,8 @@ module.exports.http = {
|
||||||
'favicon'
|
'favicon'
|
||||||
],
|
],
|
||||||
rateLimit: rateLimiter,
|
rateLimit: rateLimiter,
|
||||||
|
passportInit: require('passport').initialize(),
|
||||||
|
passportSession: require('passport').session(),
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* *
|
* *
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Passport configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports.passport = {
|
||||||
|
local: {
|
||||||
|
strategy: require('passport-local').Strategy
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,21 @@ module.exports.policies = {
|
||||||
* *
|
* *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
// '*': true,
|
'*': true,
|
||||||
|
|
||||||
};
|
UserController: {
|
||||||
|
'*': true,
|
||||||
|
update: [ 'sessionAuth' ],
|
||||||
|
me: [ 'sessionAuth' ]
|
||||||
|
},
|
||||||
|
|
||||||
|
AuthController: {
|
||||||
|
'*': true,
|
||||||
|
logout: [ 'sessionAuth' ],
|
||||||
|
disconnect: [ 'sessionAuth' ]
|
||||||
|
},
|
||||||
|
|
||||||
|
TargetController: {
|
||||||
|
'*': [ 'sessionAuth' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Passport protocol configurations
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const base64URL = require('base64url')
|
||||||
|
|
||||||
|
module.exports.protocols = {
|
||||||
|
local: {
|
||||||
|
/**
|
||||||
|
* Validate a login request
|
||||||
|
*
|
||||||
|
* Looks up a user using the supplied identifier (email or username) and then
|
||||||
|
* attempts to find a local Passport associated with the user. If a Passport is
|
||||||
|
* found, its password is checked against the password supplied in the form.
|
||||||
|
*
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {string} identifier
|
||||||
|
* @param {string} password
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
login: async function (req, identifier, password, next) {
|
||||||
|
if (!validateEmail(identifier)) {
|
||||||
|
return next(new Error('invalid email address'), false)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: identifier
|
||||||
|
})
|
||||||
|
if (!user) throw new Error('an account with that email was not found')
|
||||||
|
|
||||||
|
const passport = await Passport.findOne({
|
||||||
|
protocol: 'local',
|
||||||
|
user: user.id
|
||||||
|
})
|
||||||
|
if (passport) {
|
||||||
|
const res = await Passport.validatePassword(password, passport)
|
||||||
|
if (!res) throw new Error('incorrect password')
|
||||||
|
return next(null, user)
|
||||||
|
} else {
|
||||||
|
throw new Error('that account does not have password login enabled')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return next(e, false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
register: async function (user, next) {
|
||||||
|
try {
|
||||||
|
const token = generateToken()
|
||||||
|
const password = user.password
|
||||||
|
if (!password.length) throw new Error('password cannot be blank')
|
||||||
|
delete user.password
|
||||||
|
|
||||||
|
const newUser = await User.create(user).fetch()
|
||||||
|
try {
|
||||||
|
await Passport.create({
|
||||||
|
protocol: 'local',
|
||||||
|
password,
|
||||||
|
user: newUser.id,
|
||||||
|
accessToken: token
|
||||||
|
})
|
||||||
|
return next(null, token)
|
||||||
|
} catch (e) {
|
||||||
|
await User.destroy(newUser.id)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return next(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: async function (user, next) {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
},
|
||||||
|
connect: async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const user = req.user
|
||||||
|
const password = req.param('password')
|
||||||
|
|
||||||
|
const pass = await Passport.findOne({
|
||||||
|
protocol: 'local',
|
||||||
|
user: user.id
|
||||||
|
})
|
||||||
|
if (!pass) {
|
||||||
|
await Passport.create({
|
||||||
|
protocol: 'local',
|
||||||
|
password,
|
||||||
|
user: user.id
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return next(null, user)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMAIL_REGEX = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i
|
||||||
|
|
||||||
|
function validateEmail (email) {
|
||||||
|
return EMAIL_REGEX.test(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateToken () {
|
||||||
|
return base64URL(crypto.randomBytes(48))
|
||||||
|
}
|
|
@ -22,15 +22,16 @@ module.exports.routes = {
|
||||||
* *
|
* *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
'/': {
|
'GET /': {
|
||||||
view: 'pages/index'
|
view: 'pages/index'
|
||||||
},
|
},
|
||||||
'/login': {
|
'GET /login': {
|
||||||
view: 'pages/login'
|
view: 'pages/login'
|
||||||
},
|
},
|
||||||
'/register': {
|
'GET /register': {
|
||||||
view: 'pages/login'
|
view: 'pages/login'
|
||||||
},
|
},
|
||||||
|
'GET /app': 'TargetController.show',
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* *
|
* *
|
||||||
|
@ -47,15 +48,25 @@ module.exports.routes = {
|
||||||
// ╠═╣╠═╝║ ║╣ ║║║ ║║╠═╝║ ║║║║║ ║ ╚═╗
|
// ╠═╣╠═╝║ ║╣ ║║║ ║║╠═╝║ ║║║║║ ║ ╚═╗
|
||||||
// ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝
|
// ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝
|
||||||
|
|
||||||
'POST /api/publish': {
|
'POST /register': 'UserController.create',
|
||||||
controller: 'books',
|
'GET /logout': 'AuthController.logout',
|
||||||
action: 'publish'
|
|
||||||
},
|
|
||||||
|
|
||||||
'GET /api/books': {
|
'POST /auth/email_exists': 'AuthController.emailExists',
|
||||||
controller: 'books',
|
'POST /auth/email_available': 'AuthController.emailAvailable',
|
||||||
action: 'list'
|
// 'POST /auth/local': 'AuthController.callback',
|
||||||
},
|
// 'POST /auth/local/:action': 'AuthController.callback',
|
||||||
|
|
||||||
|
'POST /auth/:provider': 'AuthController.callback',
|
||||||
|
'POST /auth/:provider/:action': 'AuthController.callback',
|
||||||
|
|
||||||
|
'GET /auth/:provider': 'AuthController.provider',
|
||||||
|
'GET /auth/:provider/callback': 'AuthController.callback',
|
||||||
|
'GET /auth/:provider/:action': 'AuthController.callback',
|
||||||
|
|
||||||
|
'POST /api/publish': 'BooksController.publish',
|
||||||
|
|
||||||
|
'GET /api/books': 'BooksController.list',
|
||||||
|
'GET /api/me': 'UserController.me',
|
||||||
|
|
||||||
|
|
||||||
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
|
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
|
||||||
|
|
14
package.json
14
package.json
|
@ -9,9 +9,15 @@
|
||||||
"@sailshq/lodash": "^3.10.3",
|
"@sailshq/lodash": "^3.10.3",
|
||||||
"@sailshq/socket.io-redis": "^5.2.0",
|
"@sailshq/socket.io-redis": "^5.2.0",
|
||||||
"async": "2.0.1",
|
"async": "2.0.1",
|
||||||
|
"base64url": "^3.0.0",
|
||||||
|
"bcrypt": "^3.0.2",
|
||||||
"express-rate-limit": "^3.2.1",
|
"express-rate-limit": "^3.2.1",
|
||||||
"forever": "^0.15.3",
|
"forever": "^0.15.3",
|
||||||
"grunt": "^1.0.3",
|
"grunt": "^1.0.3",
|
||||||
|
"passport": "^0.4.0",
|
||||||
|
"passport-github2": "^0.1.11",
|
||||||
|
"passport-google-oauth20": "^1.0.0",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
"react": "^16.6.0",
|
"react": "^16.6.0",
|
||||||
"react-dom": "^16.6.0",
|
"react-dom": "^16.6.0",
|
||||||
"sails": "^1.0.2",
|
"sails": "^1.0.2",
|
||||||
|
@ -26,7 +32,6 @@
|
||||||
"@babel/polyfill": "^7.0.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@babel/preset-env": "^7.1.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@sailshq/eslint": "^4.19.3",
|
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.4",
|
||||||
"css-loader": "^1.0.1",
|
"css-loader": "^1.0.1",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
@ -41,7 +46,7 @@
|
||||||
"webpack-dev-server": "^3.1.10"
|
"webpack-dev-server": "^3.1.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run open:client",
|
"start": "npm-run-all --parallel open:client lift",
|
||||||
"start:debug": "npm-run-all --parallel open:client debug",
|
"start:debug": "npm-run-all --parallel open:client debug",
|
||||||
"start:prod": "npm-run-all --parallel build:prod lift",
|
"start:prod": "npm-run-all --parallel build:prod lift",
|
||||||
"open:client": "webpack-dev-server --mode development",
|
"open:client": "webpack-dev-server --mode development",
|
||||||
|
@ -65,5 +70,10 @@
|
||||||
"license": "",
|
"license": "",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.10"
|
"node": ">=8.10"
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"globals": [
|
||||||
|
"sails"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ setTimeout(function sunrise () {
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 id="main-title" class="container"><%= __('A brand new app.') %></h1>
|
<h1 id="main-title" class="container"><%= __('A brand new app.') %></h1>
|
||||||
<h3 class="container">You're looking at: <code><%= view.pathFromApp + '.' +view.ext %></code></h3>
|
<h3 class="container">You're looking at: <code><%= view.pathFromApp + '.' +view.ext %></code></h3>
|
||||||
<h3>Go to Login: <a href="/login">Here</a></h3>
|
<h3 class="container">Go to Login: <a href="/login">Here</a></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="main container clearfix">
|
<div class="main container clearfix">
|
||||||
<ul class="getting-started">
|
<ul class="getting-started">
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
authed: <%- email %><br />
|
||||||
|
<a href="/logout">Logout</a>
|
Loading…
Reference in New Issue