Merge pull request #16 from EbookFoundation/staging

Travis, testing, added build badge in the readme
pull/26/head
Michael Iacona 2018-11-01 18:37:50 -04:00 committed by GitHub
commit 48f8f9f563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 783 additions and 37 deletions

View File

@ -1,6 +1,8 @@
{
"generators": {
"modules": {}
"modules": {
"permissions-api": "sails-permissions/generator"
}
},
"_generatedWith": {
"sails": "1.0.2",

14
.travis.yml Normal file
View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
show: function (req, res) {
res.view('pages/temp', {
email: req.user.email
})
}
}

View File

@ -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)
}
}

171
api/helpers/passport.js Normal file
View File

@ -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
}
}

View File

@ -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

View File

@ -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'},

72
api/models/Passport.js Normal file
View File

@ -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)
}
}

36
api/models/User.js Normal file
View File

@ -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
}
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
// ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗
// ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
}
}

View File

@ -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')
}

View File

@ -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))
}

View File

@ -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))
}
}

View File

@ -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' />

View File

@ -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;

View File

@ -84,4 +84,9 @@
}
}
}
& + .underlined-input,
& + .underlined-input-readonly {
margin-top: 8px
}
}

View File

@ -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>

5
config/auth.js Normal file
View File

@ -0,0 +1,5 @@
module.exports.auth = {
bcrypt: {
rounds: 8
}
}

View File

@ -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(),
/***************************************************************************
* *

9
config/passport.js Normal file
View File

@ -0,0 +1,9 @@
/**
* Passport configuration
*/
module.exports.passport = {
local: {
strategy: require('passport-local').Strategy
}
}

View File

@ -17,6 +17,21 @@ module.exports.policies = {
* *
***************************************************************************/
// '*': true,
'*': true,
};
UserController: {
'*': true,
update: [ 'sessionAuth' ],
me: [ 'sessionAuth' ]
},
AuthController: {
'*': true,
logout: [ 'sessionAuth' ],
disconnect: [ 'sessionAuth' ]
},
TargetController: {
'*': [ 'sessionAuth' ]
}
}

104
config/protocols.js Normal file
View File

@ -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))
}

View File

@ -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',
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗

View File

@ -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"
]
}
}

View File

@ -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">

2
views/pages/temp.ejs Normal file
View File

@ -0,0 +1,2 @@
authed: <%- email %><br />
<a href="/logout">Logout</a>