mirror of
https://github.com/wassname/talk.git
synced 2026-09-11 12:51:33 +08:00
Initial pass at email confirmation
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ const ActionSchema = new Schema({
|
||||
item_type: String,
|
||||
item_id: String,
|
||||
user_id: String,
|
||||
metadata: Object, //Holds arbitrary metadata about the action.
|
||||
metadata: Schema.Types.Mixed
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
|
||||
+5
-16
@@ -1,7 +1,6 @@
|
||||
const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const _ = require('lodash');
|
||||
const cache = require('../services/cache');
|
||||
|
||||
const WordlistSchema = new Schema({
|
||||
banned: [String],
|
||||
@@ -53,6 +52,10 @@ const SettingSchema = new Schema({
|
||||
charCountEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requireEmailConfirmation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}, {
|
||||
timestamps: {
|
||||
@@ -121,19 +124,11 @@ const SettingService = module.exports = {};
|
||||
*/
|
||||
const selector = {id: '1'};
|
||||
|
||||
/**
|
||||
* Cache expiry time in seconds for when the cached entry of the settings object
|
||||
* expires. 2 minutes.
|
||||
*/
|
||||
const EXPIRY_TIME = 60 * 2;
|
||||
|
||||
/**
|
||||
* Gets the entire settings record and sends it back
|
||||
* @return {Promise} settings the whole settings record
|
||||
*/
|
||||
SettingService.retrieve = () => cache.wrap('settings', EXPIRY_TIME, () => {
|
||||
return Setting.findOne(selector);
|
||||
}).then((setting) => new Setting(setting));
|
||||
SettingService.retrieve = () => Setting.findOne(selector);
|
||||
|
||||
/**
|
||||
* This will update the settings object with whatever you pass in
|
||||
@@ -146,12 +141,6 @@ SettingService.update = (settings) => Setting.findOneAndUpdate(selector, {
|
||||
upsert: true,
|
||||
new: true,
|
||||
setDefaultsOnInsert: true
|
||||
}).then((settings) => {
|
||||
|
||||
// Invalidate the settings cache.
|
||||
return cache
|
||||
.set('settings', settings, EXPIRY_TIME)
|
||||
.then(() => settings);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
+59
-55
@@ -4,7 +4,6 @@ const _ = require('lodash');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const Action = require('./action');
|
||||
|
||||
const Comment = require('./comment');
|
||||
|
||||
// SALT_ROUNDS is the number of rounds that the bcrypt algorithm will run
|
||||
@@ -31,6 +30,37 @@ if (process.env.NODE_ENV === 'test' && !process.env.TALK_SESSION_SECRET) {
|
||||
throw new Error('TALK_SESSION_SECRET must be defined to encode JSON Web Tokens and other auth functionality');
|
||||
}
|
||||
|
||||
// ProfileSchema is the mongoose schema defined as the representation of a
|
||||
// User's profile stored in MongoDB.
|
||||
const ProfileSchema = new mongoose.Schema({
|
||||
|
||||
// ID provides the identifier for the user profile, in the case of a local
|
||||
// provider, the id would be an email, in the case of a social provider,
|
||||
// the id would be the foreign providers identifier.
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Provider is simply the name attached to the authentication mode. In the
|
||||
// case of a locally provided profile, this will simply be `local`, or a
|
||||
// social provider which for Facebook would just be `facebook`.
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Metadata provides a place to put provider specific details. An example of
|
||||
// something that could be stored here is the `metadata.confirmed_at` could be
|
||||
// used by the `local` provider to indicate when the email address was
|
||||
// confirmed.
|
||||
metadata: {
|
||||
type: mongoose.Schema.Types.Mixed
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
});
|
||||
|
||||
// UserSchema is the mongoose schema defined as the representation of a User in
|
||||
// MongoDB.
|
||||
const UserSchema = new mongoose.Schema({
|
||||
@@ -60,26 +90,7 @@ const UserSchema = new mongoose.Schema({
|
||||
// Profiles describes the array of identities for a given user. Any one user
|
||||
// can have multiple profiles associated with them, including multiple email
|
||||
// addresses.
|
||||
profiles: [new mongoose.Schema({
|
||||
|
||||
// ID provides the identifier for the user profile, in the case of a local
|
||||
// provider, the id would be an email, in the case of a social provider,
|
||||
// the id would be the foreign providers identifier.
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Provider is simply the name attached to the authentication mode. In the
|
||||
// case of a locally provided profile, this will simply be `local`, or a
|
||||
// social provider which for Facebook would just be `facebook`.
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
})],
|
||||
profiles: [ProfileSchema],
|
||||
|
||||
// Roles provides an array of roles (as strings) that is associated with a
|
||||
// user.
|
||||
@@ -499,45 +510,43 @@ UserService.createPasswordResetToken = function (email) {
|
||||
email = email.toLowerCase();
|
||||
|
||||
return UserModel.findOne({profiles: {$elemMatch: {id: email}}})
|
||||
.then(user => {
|
||||
.then((user) => {
|
||||
if (!user) {
|
||||
|
||||
if (user === null) {
|
||||
|
||||
// since we don't want to reveal that the email does/doesn't exist
|
||||
// just go ahead and resolve the Promise with null and check in the endpoint
|
||||
return Promise.resolve(null);
|
||||
// Since we don't want to reveal that the email does/doesn't exist
|
||||
// just go ahead and resolve the Promise with null and check in the
|
||||
// endpoint.
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {email, jti: uuid.v4(), userId: user.id, version: user.__v};
|
||||
const token = jwt.sign(payload, process.env.TALK_SESSION_SECRET, {expiresIn: '1d'});
|
||||
const payload = {
|
||||
jti: uuid.v4(),
|
||||
email,
|
||||
userId: user.id,
|
||||
version: user.__v
|
||||
};
|
||||
|
||||
return token;
|
||||
return jwt.sign(payload, process.env.TALK_SESSION_SECRET, {algorithm: 'HS256'}, {
|
||||
expiresIn: '1d'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* verifies a jwt and returns the associated user
|
||||
* Verifies a jwt and returns the associated user.
|
||||
* @param {String} token the JSON Web Token to verify
|
||||
*/
|
||||
UserService.verifyPasswordResetToken = token => {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, process.env.TALK_SESSION_SECRET, (error, decoded) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
jwt.verify(token, process.env.TALK_SESSION_SECRET, (err, decoded) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(decoded);
|
||||
});
|
||||
})
|
||||
.then(decoded => {
|
||||
|
||||
/**
|
||||
* TODO: check the jti from this decoded token in redis
|
||||
* and make an entry if it does not exist.
|
||||
* reject if entry already exists.
|
||||
*/
|
||||
return UserService.findById(decoded.userId);
|
||||
});
|
||||
.then(decoded => UserService.findById(decoded.userId));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -594,18 +603,13 @@ UserService.all = () => {
|
||||
* Adds a new User bio
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
UserService.addBio = (id, bio) => (
|
||||
UserModel.findOneAndUpdate({
|
||||
id
|
||||
}, {
|
||||
$set: {
|
||||
'settings.bio': bio
|
||||
}
|
||||
}, {
|
||||
new: true
|
||||
})
|
||||
);
|
||||
UserService.addBio = (id, bio) => UserModel.update({
|
||||
id
|
||||
}, {
|
||||
$set: {
|
||||
'settings.bio': bio
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add an action to the user.
|
||||
|
||||
Reference in New Issue
Block a user