mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 09:22:01 +08:00
Merge branch 'passport' of github.com:coralproject/talk into passport
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();
|
||||
});
|
||||
});
|
||||
+36
-58
@@ -12,6 +12,7 @@ const UserSchema = new mongoose.Schema({
|
||||
required: true
|
||||
},
|
||||
displayName: String,
|
||||
photo: String,
|
||||
disabled: Boolean,
|
||||
password: String,
|
||||
profiles: [{
|
||||
@@ -25,11 +26,6 @@ const UserSchema = new mongoose.Schema({
|
||||
}
|
||||
}],
|
||||
roles: [String]
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
}
|
||||
});
|
||||
|
||||
// Add the indixies on the user profile data.
|
||||
@@ -57,22 +53,6 @@ UserSchema.options.toJSON.transform = (doc, ret, options) => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* toObject overrides to remove the password field from the toObject
|
||||
* output.
|
||||
*/
|
||||
UserSchema.options.toObject = {};
|
||||
UserSchema.options.toObject.hide = 'password';
|
||||
UserSchema.options.toObject.transform = (doc, ret, options) => {
|
||||
if (options.hide) {
|
||||
options.hide.split(' ').forEach((prop) => {
|
||||
delete ret[prop];
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a user given their email address that we have for them in the system
|
||||
* and ensures that the retuned user matches the password passed in as well.
|
||||
@@ -121,21 +101,19 @@ UserSchema.statics.findLocalUser = function(email, password) {
|
||||
UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
let srcUser, dstUser;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
User.findOne({id: dstUserID}).exec(),
|
||||
User.findOne({id: srcUserID}).exec()
|
||||
])
|
||||
.then((users) => {
|
||||
dstUser = users[0];
|
||||
srcUser = users[1];
|
||||
return Promise.all([
|
||||
User.findOne({id: dstUserID}).exec(),
|
||||
User.findOne({id: srcUserID}).exec()
|
||||
]).then((users) => {
|
||||
dstUser = users[0];
|
||||
srcUser = users[1];
|
||||
|
||||
srcUser.profiles.forEach((profile) => {
|
||||
dstUser.profiles.push(profile);
|
||||
});
|
||||
srcUser.profiles.forEach((profile) => {
|
||||
dstUser.profiles.push(profile);
|
||||
});
|
||||
|
||||
return srcUser.remove();
|
||||
})
|
||||
return srcUser.remove();
|
||||
})
|
||||
.then(() => dstUser.save());
|
||||
};
|
||||
|
||||
@@ -146,34 +124,34 @@ UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
* @param {Function} done [description]
|
||||
*/
|
||||
UserSchema.statics.findOrCreateExternalUser = function(profile) {
|
||||
return User
|
||||
.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
return User.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
displayName: profile.displayName,
|
||||
roles: [],
|
||||
photo: Array.isArray(profile.photos) && profile.photos.length > 0 ? profile.photos[0].value : null,
|
||||
profiles: [
|
||||
{
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
displayName: profile.displayName,
|
||||
roles: [],
|
||||
profiles: [
|
||||
{
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return user.save();
|
||||
]
|
||||
});
|
||||
|
||||
return user.save();
|
||||
});
|
||||
};
|
||||
|
||||
UserSchema.statics.changePassword = function(id, password) {
|
||||
|
||||
+2
-1
@@ -65,7 +65,8 @@ 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`,
|
||||
profileFields: ['id', 'displayName', 'picture.type(large)']
|
||||
}, (accessToken, refreshToken, profile, done) => {
|
||||
User
|
||||
.findOrCreateExternalUser(profile)
|
||||
|
||||
+69
-18
@@ -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);
|
||||
});
|
||||
|
||||
/**
|
||||
* This destroys the session of a user, if they have one.
|
||||
*/
|
||||
router.delete('/', (req, res) => {
|
||||
req.logout();
|
||||
res.status(204).end();
|
||||
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', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -109,7 +109,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