mirror of
https://github.com/wassname/talk.git
synced 2026-08-20 12:50:41 +08:00
Added facebook login support + redis cache
This commit is contained in:
@@ -19,9 +19,12 @@ Runs Talk.
|
||||
The Talk application requires specific configuration options to be available
|
||||
inside the environment in order to run, those variables are listed here:
|
||||
|
||||
- `TALK_SESSION_SECRET` (*required*) -
|
||||
- `TALK_FACEBOOK_APP_ID` (*required*) -
|
||||
- `TALK_FACEBOOK_APP_SECRET` (*required*) -
|
||||
- `TALK_SESSION_SECRET` (*required*) - a random string which will be used to
|
||||
secure cookies
|
||||
- `TALK_FACEBOOK_APP_ID` (*required*) - the Facebook app id for your Facebook
|
||||
Login enabled app.
|
||||
- `TALK_FACEBOOK_APP_SECRET` (*required*) - the Facebook app secret for your
|
||||
Facebook Login enabled app.
|
||||
- `TALK_ROOT_URL` (*required*) - Root url of the installed application externally available in the format: `<scheme>://<host>` without the path.
|
||||
|
||||
### Running with Docker
|
||||
|
||||
@@ -38,6 +38,7 @@ const session_opts = {
|
||||
rolling: true,
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
name: 'talk.sid',
|
||||
cookie: {
|
||||
secure: false,
|
||||
maxAge: 18000000, // 30 minutes for expiry.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
const redis = require('./redis');
|
||||
|
||||
const cache = module.exports = {};
|
||||
|
||||
/**
|
||||
* This collects a key that may either be an array or a string and creates a
|
||||
* unified key out of it.
|
||||
* @param {Mixed} key Either an array of items composing a key or a string
|
||||
* @return {String} A string that represents a key
|
||||
*/
|
||||
const keyfunc = (key) => {
|
||||
if (Array.isArray(key)) {
|
||||
return `cache[${key.join(':')}]`;
|
||||
}
|
||||
|
||||
return `cache[${key}]`;
|
||||
};
|
||||
|
||||
/**
|
||||
* This wraps a complicated function with a cache, in the event that the item is
|
||||
* not inside the cache, it will perform the work to get it and then set it
|
||||
* followed by returning the value.
|
||||
* @param {Mixed} key Either an array of items or string represening this
|
||||
* work
|
||||
* @param {Integer} expiry Time in seconds for the cache entry to live for
|
||||
* @param {Function} work A function that returns a promise that can be
|
||||
* resolved as the value to cache.
|
||||
* @return {Promise} Resolves to the value either retrieved from cache
|
||||
*/
|
||||
cache.wrap = (key, expiry, work) => {
|
||||
return cache
|
||||
.get(key)
|
||||
.then((value) => {
|
||||
if (value !== null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return work()
|
||||
.then((value) => {
|
||||
return cache
|
||||
.set(key, value, expiry)
|
||||
.then(() => value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This returns a promise that returns a promise that resolves with the value
|
||||
* from the cache or null if it does not exist in the cache.
|
||||
* @param {Mixed} key Either an array of items composing a key or a string
|
||||
* @return {Promise}
|
||||
*/
|
||||
cache.get = (key) => new Promise((resolve, reject) => {
|
||||
redis.get(keyfunc(key), (err, reply) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (reply !== null) {
|
||||
let value;
|
||||
|
||||
try {
|
||||
|
||||
// Parse the stored cache value from JSON.
|
||||
value = JSON.parse(reply);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
return resolve(value);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This sets a value on the key with the expiry and then resolves once it is
|
||||
* done.
|
||||
* @param {Mixed} key Either an array of items composing a key or a string
|
||||
* @param {Mixed} value Object to be serialized and set to the cache
|
||||
* @param {Integer} expiry Time in seconds for the cache entry to live for
|
||||
* @return {Promise}
|
||||
*/
|
||||
cache.set = (key, value, expiry) => new Promise((resolve, reject) => {
|
||||
|
||||
// Serialize the value as JSON.
|
||||
let reply = JSON.stringify(value);
|
||||
|
||||
redis.set(keyfunc(key), reply, 'EX', expiry, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -65,7 +65,7 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET &&
|
||||
passport.use(new FacebookStrategy({
|
||||
clientID: process.env.TALK_FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
|
||||
callbackURL: `${process.env.TALK_ROOT_URL}/connect/facebook/callback`
|
||||
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`
|
||||
}, (accessToken, refreshToken, profile, done) => {
|
||||
User
|
||||
.findOrCreateExternalUser(profile)
|
||||
|
||||
+70
-19
@@ -4,40 +4,91 @@ const authorization = require('../../../middleware/authorization');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* This returns the user if they are logged in.
|
||||
*/
|
||||
router.get('/', authorization.needed(), (req, res) => {
|
||||
res.json(req.user);
|
||||
});
|
||||
|
||||
router.delete('/', (req, res) => {
|
||||
req.logout();
|
||||
res.status(204).end();
|
||||
/**
|
||||
* This destroys the session of a user, if they have one.
|
||||
*/
|
||||
router.delete('/', authorization.needed(), (req, res) => {
|
||||
req.session.destroy(() => {
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This sends back the user data as JSON.
|
||||
*/
|
||||
const HandleAuthCallback = (req, res, next) => (err, user) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return next(authorization.ErrNotAuthorized);
|
||||
}
|
||||
|
||||
// Perform the login of the user!
|
||||
req.logIn(user, (err) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// We logged in the user! Let's send back the user data.
|
||||
res.json({user});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the response to the login attempt via a popup callback with some JS.
|
||||
*/
|
||||
const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
|
||||
if (err) {
|
||||
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.render('auth-callback', {err: JSON.stringify(authorization.ErrNotAuthorized), data: null});
|
||||
}
|
||||
|
||||
// Perform the login of the user!
|
||||
req.logIn(user, (err) => {
|
||||
if (err) {
|
||||
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
|
||||
}
|
||||
|
||||
// We logged in the user! Let's send back the user data.
|
||||
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Local auth endpoint, will recieve a email and password
|
||||
*/
|
||||
router.post('/local', (req, res, next) => {
|
||||
|
||||
// Perform the local authentication.
|
||||
passport.authenticate('local', (err, user) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
passport.authenticate('local', HandleAuthCallback(req, res, next))(req, res, next);
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return next(authorization.ErrNotAuthorized);
|
||||
}
|
||||
/**
|
||||
* Facebook auth endpoint, this will redirect the user immediatly to facebook
|
||||
* for authorization.
|
||||
*/
|
||||
router.get('/facebook', passport.authenticate('facebook'));
|
||||
|
||||
// Perform the login of the user!
|
||||
req.logIn(user, (err) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
/**
|
||||
* Facebook callback endpoint, this will send the user a html page designed to
|
||||
* send back the user credentials upon sucesfull login.
|
||||
*/
|
||||
router.get('/facebook/callback', (req, res, next) => {
|
||||
|
||||
// We logged in the user! Let's send back the user data.
|
||||
res.json({user});
|
||||
});
|
||||
})(req, res, next);
|
||||
// Perform the facebook login flow and pass the data back through the opener.
|
||||
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -94,7 +94,7 @@ router.post('/:comment_id', (req, res, next) => {
|
||||
});
|
||||
|
||||
router.post('/:comment_id/status', (req, res, next) => {
|
||||
|
||||
|
||||
Comment
|
||||
.changeStatus(req.params.comment_id, req.body.status)
|
||||
.then(comment => res.status(200).send(comment))
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
window.opener.authCallback(<% if (err) { %>'<%- err %>'<% } else { %>null<% } %>, '<%- data %>');
|
||||
setTimeout(function() { window.close(); }, 50);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user