Merge pull request #16 from EbookFoundation/staging
Travis, testing, added build badge in the readmepull/26/head
commit
48f8f9f563
4
.sailsrc
4
.sailsrc
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"generators": {
|
||||
"modules": {}
|
||||
"modules": {
|
||||
"permissions-api": "sails-permissions/generator"
|
||||
}
|
||||
},
|
||||
"_generatedWith": {
|
||||
"sails": "1.0.2",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
deploy:
|
||||
provider: elasticbeanstalk
|
||||
access_key_id: "Encrypted <access-key-id>="
|
||||
secret_access_key:
|
||||
secure: "Encypted <secret-access-key>="
|
||||
region: "us-east-1"
|
||||
app: "example-app-name"
|
||||
env: "example-app-environment"
|
||||
bucket_name: "the-target-S3-bucket"
|
||||
|
||||
#script: node testfile
|
|
@ -25,3 +25,5 @@ This app was originally generated on Tue Oct 16 2018 01:21:31 GMT+0000 (UTC) usi
|
|||
Note: Generators are usually run using the globally-installed `sails` CLI (command-line interface). This CLI version is _environment-specific_ rather than app-specific, thus over time, as a project's dependencies are upgraded or the project is worked on by different developers on different computers using different versions of Node.js, the Sails dependency in its package.json file may differ from the globally-installed Sails CLI release it was originally generated with. (Be sure to always check out the relevant [upgrading guides](https://sailsjs.com/upgrading) before upgrading the version of Sails used by your app. If you're stuck, [get help here](https://sailsjs.com/support).)
|
||||
-->
|
||||
|
||||
[![Build Status](https://travis-ci.org/miacona96/RoE-pipe.svg?branch=master)](https://travis-ci.org/miacona96/RoE-pipe)
|
||||
|
||||
|
|
|
@ -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},
|
||||
author: {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
|
||||
})
|
||||
|
||||
export const setCarousel = pos => ({
|
||||
type: ACTIONS.set_carousel,
|
||||
data: pos
|
||||
})
|
||||
export const setCarousel = pos => (dispatch, getState) => {
|
||||
dispatch(clearError())
|
||||
dispatch({
|
||||
type: ACTIONS.set_carousel,
|
||||
data: pos
|
||||
})
|
||||
}
|
||||
|
||||
export const setError = data => ({
|
||||
type: ACTIONS.set_error,
|
||||
|
@ -43,22 +46,35 @@ export const clearError = () => ({
|
|||
})
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
export const checkEmail = email => (dispatch, getState) => {
|
||||
// dispatch(setWorking(true))
|
||||
export const checkEmail = email => async (dispatch, getState) => {
|
||||
dispatch(setWorking(true))
|
||||
dispatch(clearError())
|
||||
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 {
|
||||
dispatch(setError({
|
||||
type: 'email',
|
||||
error: 'Please enter a valid email address.'
|
||||
}))
|
||||
}
|
||||
// dispatch(setWorking(false))
|
||||
dispatch(setWorking(false))
|
||||
}
|
||||
|
||||
export const checkPassword = (email, password) => async (dispatch, getState) => {
|
||||
|
@ -67,10 +83,9 @@ export const checkPassword = (email, password) => async (dispatch, getState) =>
|
|||
// do email + password check
|
||||
try {
|
||||
const res = await Ajax.post({
|
||||
url: '/api/token',
|
||||
url: '/auth/local',
|
||||
data: {
|
||||
grant_type: 'credentials',
|
||||
email,
|
||||
identifier: email,
|
||||
password
|
||||
}
|
||||
})
|
||||
|
@ -84,3 +99,37 @@ export const checkPassword = (email, password) => async (dispatch, getState) =>
|
|||
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) {
|
||||
reject(new AjaxError(e.toString(), data, xhr))
|
||||
} 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 UnderlineInput from './components/UnderlineInput'
|
||||
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'
|
||||
|
||||
|
@ -55,7 +55,7 @@ class App extends React.Component {
|
|||
getPasswordInputs () {
|
||||
return [
|
||||
<UnderlineInput
|
||||
key={0}
|
||||
key={1}
|
||||
type='password'
|
||||
name='password'
|
||||
placeholder='Password'
|
||||
|
@ -81,10 +81,10 @@ class App extends React.Component {
|
|||
<Carousel position={this.state.carouselPosition} >
|
||||
<CarouselItem
|
||||
header='Sign up'
|
||||
inputs={this.getEmailInputs()}
|
||||
inputs={this.getEmailInputs().concat(this.getPasswordInputs())}
|
||||
error={this.state.emailError}
|
||||
button='Sign up'
|
||||
onButtonClick={() => null}
|
||||
onButtonClick={() => this.dispatch(signup(this.state.user.email, this.state.user.password))}
|
||||
smallButton='Have an account?'
|
||||
onSmallButtonClick={() => this.dispatch(setCarousel(1))}
|
||||
footer='Sign up with your Google account' />
|
||||
|
|
|
@ -22,8 +22,8 @@ $background-2: white;
|
|||
$text-dark-1: $black-1;
|
||||
$text-dark-2: $black-2;
|
||||
|
||||
$accent-1: #4423c4;
|
||||
$accent-2: #4460c4;
|
||||
$accent-1: #731212;
|
||||
$accent-2: #9a834d;
|
||||
$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' } } %>
|
||||
<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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports.auth = {
|
||||
bcrypt: {
|
||||
rounds: 8
|
||||
}
|
||||
}
|
|
@ -42,6 +42,8 @@ module.exports.http = {
|
|||
'rateLimit',
|
||||
'cookieParser',
|
||||
'session',
|
||||
'passportInit',
|
||||
'passportSession',
|
||||
'bodyParser',
|
||||
'compress',
|
||||
'poweredBy',
|
||||
|
@ -50,6 +52,8 @@ module.exports.http = {
|
|||
'favicon'
|
||||
],
|
||||
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'
|
||||
},
|
||||
'/login': {
|
||||
'GET /login': {
|
||||
view: 'pages/login'
|
||||
},
|
||||
'/register': {
|
||||
'GET /register': {
|
||||
view: 'pages/login'
|
||||
},
|
||||
'GET /app': 'TargetController.show',
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
|
@ -47,15 +48,25 @@ module.exports.routes = {
|
|||
// ╠═╣╠═╝║ ║╣ ║║║ ║║╠═╝║ ║║║║║ ║ ╚═╗
|
||||
// ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝
|
||||
|
||||
'POST /api/publish': {
|
||||
controller: 'books',
|
||||
action: 'publish'
|
||||
},
|
||||
'POST /register': 'UserController.create',
|
||||
'GET /logout': 'AuthController.logout',
|
||||
|
||||
'GET /api/books': {
|
||||
controller: 'books',
|
||||
action: 'list'
|
||||
},
|
||||
'POST /auth/email_exists': 'AuthController.emailExists',
|
||||
'POST /auth/email_available': 'AuthController.emailAvailable',
|
||||
// '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/socket.io-redis": "^5.2.0",
|
||||
"async": "2.0.1",
|
||||
"base64url": "^3.0.0",
|
||||
"bcrypt": "^3.0.2",
|
||||
"express-rate-limit": "^3.2.1",
|
||||
"forever": "^0.15.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-dom": "^16.6.0",
|
||||
"sails": "^1.0.2",
|
||||
|
@ -26,7 +32,6 @@
|
|||
"@babel/polyfill": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@sailshq/eslint": "^4.19.3",
|
||||
"babel-loader": "^8.0.4",
|
||||
"css-loader": "^1.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
|
@ -41,7 +46,7 @@
|
|||
"webpack-dev-server": "^3.1.10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run open:client",
|
||||
"start": "npm-run-all --parallel open:client lift",
|
||||
"start:debug": "npm-run-all --parallel open:client debug",
|
||||
"start:prod": "npm-run-all --parallel build:prod lift",
|
||||
"open:client": "webpack-dev-server --mode development",
|
||||
|
@ -65,5 +70,10 @@
|
|||
"license": "",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"standard": {
|
||||
"globals": [
|
||||
"sails"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ setTimeout(function sunrise () {
|
|||
<div class="header">
|
||||
<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>Go to Login: <a href="/login">Here</a></h3>
|
||||
<h3 class="container">Go to Login: <a href="/login">Here</a></h3>
|
||||
</div>
|
||||
<div class="main container clearfix">
|
||||
<ul class="getting-started">
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
authed: <%- email %><br />
|
||||
<a href="/logout">Logout</a>
|
Loading…
Reference in New Issue