Merge branch 'master' into extract-css-n-caching

This commit is contained in:
Kiwi
2018-03-15 00:42:08 +01:00
committed by GitHub
7 changed files with 101 additions and 18 deletions
+1
View File
@@ -14,6 +14,7 @@ client/coral-framework/graphql/introspection.json
*.swp
*.DS_STORE
.prettierrc.json
.vscode
coverage/
test/e2e/reports/
@@ -76,7 +76,8 @@ class UserDetailComment extends React.Component {
</div>
</div>
<div className={styles.story}>
Story: {comment.asset.title}
{t('common.story')}:{' '}
{comment.asset.title ? comment.asset.title : comment.asset.url}
{
<Link to={`/admin/moderate/${comment.asset.id}`}>
{t('modqueue.moderate')}
@@ -109,6 +110,7 @@ class UserDetailComment extends React.Component {
<div className={styles.sideActions}>
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
{/* TODO: translate string */}
<Icon name="error_outline" /> Contains Link
</span>
</IfHasLink>
@@ -122,7 +122,8 @@ class Comment extends React.Component {
</div>
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{t('common.story')}:{' '}
{comment.asset.title ? comment.asset.title : comment.asset.url}
{!currentAsset && (
<Link to={`/admin/moderate/${comment.asset.id}`}>
{t('modqueue.moderate')}
@@ -156,6 +157,7 @@ class Comment extends React.Component {
<div className={styles.sideActions}>
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
{/* TODO: translate string */}
<Icon name="error_outline" /> Contains Link
</span>
</IfHasLink>
+2 -1
View File
@@ -4,6 +4,7 @@ const {
HandleGenerateCredentials,
HandleLogout,
} = require('../../../services/passport');
const authz = require('../../../middleware/authorization');
const router = express.Router();
/**
@@ -26,7 +27,7 @@ router.get('/', (req, res, next) => {
/**
* This blacklists the token used to authenticate.
*/
router.delete('/', HandleLogout);
router.delete('/', authz.needed(), HandleLogout);
//==============================================================================
// PASSPORT ROUTES
+13 -13
View File
@@ -184,24 +184,24 @@ async function ValidateUserLogin(loginProfile, user, done) {
* Revoke the token on the request.
*/
const HandleLogout = async (req, res, next) => {
const { jwt } = req;
const now = new Date();
const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0);
try {
const { jwt } = req;
const now = new Date();
const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0);
await client().set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry);
// Only clear the cookie on logout if enabled.
if (JWT_CLEAR_COOKIE_LOGOUT) {
debug('clearing the login cookie');
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
}
res.status(204).end();
} catch (err) {
return next(err);
}
// Only clear the cookie on logout if enabled.
if (JWT_CLEAR_COOKIE_LOGOUT) {
debug('clearing the login cookie');
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
}
res.status(204).end();
};
const checkGeneralTokenBlacklist = jwt =>
+66 -2
View File
@@ -1,7 +1,7 @@
const uuid = require('uuid');
const bcrypt = require('bcryptjs');
const errors = require('../errors');
const { some, merge } = require('lodash');
const { difference, sample, some, merge, random } = require('lodash');
const { ROOT_URL } = require('../config');
const { jwt: JWT_SECRET } = require('../secrets');
const debug = require('debug')('talk:services:users');
@@ -346,6 +346,70 @@ class UsersService {
return username.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, '');
}
/**
* Creates the initial username for an external account. Searches to make
* sure username not already used. Adds a random number if username already
* in use.
*/
static async getInitialUsername(username) {
const MAX_ATTEMPTS = 10;
const END_NUMBER_MAX = 99999;
const GROUP_ATTEMPTS = 50;
// Cast the original username.
const castedName = UsersService.castUsername(username);
const lowercaseUsername = castedName.toLowerCase();
// Try to see if our first guess has been taken.
const existingUserWithName = await UserModel.findOne({
lowercaseUsername,
});
if (!existingUserWithName) {
return castedName;
}
// Our first username was taken, lets try to find a non-taken name.
for (let i = 0; i < MAX_ATTEMPTS; i++) {
// Generate `GROUP_ATTEMPTS` guesses for the username.
const usernameGuesses = Array.from(Array(GROUP_ATTEMPTS)).map(
() => `${castedName}_${random(0, END_NUMBER_MAX)}`
);
// Map them all to lowercase.
const lowercaseUsernameGuesses = usernameGuesses.map(guess =>
guess.toLowerCase()
);
// See if any of these users aren't taken already.
const existingUsernames = (await UserModel.find(
{
lowercaseUsername: { $in: lowercaseUsernameGuesses },
},
{ lowercaseUsername: 1 }
)).map(({ lowercaseUsername }) => lowercaseUsername);
if (existingUsernames.length === lowercaseUsernameGuesses.length) {
// The number of found users is the same as the number of username
// guesses, aka, all the usernames are taken.
continue;
}
// At least one of the usernames wasn't taken! Let's filter this to only
// include unused usernames and grab one random entry from the list.
const foundLowercaseUsernameIndex = lowercaseUsernameGuesses.indexOf(
sample(difference(lowercaseUsernameGuesses, existingUsernames))
);
// Now we get the uppercase version of that string.
return usernameGuesses[foundLowercaseUsernameIndex];
}
throw new Error(
'cannot find free name after ' +
(MAX_ATTEMPTS * GROUP_ATTEMPTS + 1) +
' tries'
);
}
/**
* Finds a user given a social profile and if the user does not exist, creates
* them.
@@ -368,7 +432,7 @@ class UsersService {
// User does not exist and need to be created.
// Create an initial username for the user.
let username = UsersService.castUsername(displayName);
let username = await UsersService.getInitialUsername(displayName);
// The user was not found, lets create them!
user = new UserModel({
+13
View File
@@ -58,6 +58,19 @@ describe('services.UsersService', () => {
});
});
describe('#getInitialUsername', () => {
it('should find the first result when there is no conflict', async () => {
const username = await UsersService.getInitialUsername(
'TheGreatSockmonster'
);
expect(username).to.equal('TheGreatSockmonster');
});
it('should find a first result when there is a conflict', async () => {
const username = await UsersService.getInitialUsername('Sockmonster');
expect(username).to.match(/Sockmonster_[0-9]+/);
});
});
describe('#findPublicByIdArray()', () => {
it('should find an array of users from an array of ids', async () => {
const ids = mockUsers.map(user => user.id);