mirror of
https://github.com/wassname/talk.git
synced 2026-07-08 17:04:57 +08:00
Merge branch 'master' into bulk-actions
This commit is contained in:
+35
@@ -280,6 +280,41 @@ send data to the client. If the type in question contains args, clients may subs
|
||||
|
||||
For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions).
|
||||
|
||||
#### Field: `tokenUserNotFound`
|
||||
|
||||
```js
|
||||
tokenUserNotFound: async ({jwt, token}) => {
|
||||
let profile = await someExternalService(token);
|
||||
if (!profile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let user = await UserModel.findOneAndUpdate({
|
||||
id: profile.id
|
||||
}, {
|
||||
id: profile.id,
|
||||
username: profile.username,
|
||||
lowercaseUsername: profile.username.toLowerCase(),
|
||||
roles: [],
|
||||
profiles: []
|
||||
}, {
|
||||
setDefaultsOnInsert: true,
|
||||
new: true,
|
||||
upsert: true
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
The `tokenUserNotFound` hook allows auth integrations to hook into the event
|
||||
when a valid token is provided but a user can't be found in the database that
|
||||
matches the provided id.
|
||||
|
||||
The function is async, and should return the user object that was created in the
|
||||
database, or null if the user wasn't found. The `jwt` paramenter of the object
|
||||
is the unpacked token, while `token` is the original jwt token string.
|
||||
|
||||
#### Field: `router`
|
||||
|
||||
```js
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
const program = require('./commander');
|
||||
const parseDuration = require('parse-duration');
|
||||
const parseDuration = require('ms');
|
||||
const Table = require('cli-table');
|
||||
const AssetModel = require('../models/asset');
|
||||
const mongoose = require('../services/mongoose');
|
||||
|
||||
@@ -41,10 +41,10 @@ export default class Embed extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count count={totalCommentCount} /></Tab>
|
||||
<Tab>{t('framework.my_profile')}</Tab>
|
||||
<Tab restricted={!can(user, 'UPDATE_CONFIG')}>{t('framework.configure_stream')}</Tab>
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab} className='talk-stream-tabbar'>
|
||||
<Tab className={'talk-stream-comment-count-tab'}><Count count={totalCommentCount}/></Tab>
|
||||
<Tab className={'talk-stream-profile-tab'}>{t('framework.my_profile')}</Tab>
|
||||
<Tab className={'talk-stream-configuration-tab'} restricted={!can(user, 'UPDATE_CONFIG')}>{t('framework.configure_stream')}</Tab>
|
||||
</TabBar>
|
||||
{commentId &&
|
||||
<Button
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import styles from './Tab.css';
|
||||
|
||||
export default ({children, tabId, active, onTabClick, cStyle = 'base'}) => (
|
||||
export default ({children, tabId, active, onTabClick, cStyle = 'base', ...props}) => (
|
||||
<li
|
||||
key={tabId}
|
||||
className={active ? styles[`${cStyle}--active`] : ''}
|
||||
className={`${active ? styles[`${cStyle}--active`] : ''} tab ${props.className}`}
|
||||
onClick={() => onTabClick(tabId)}
|
||||
>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class TabBar extends React.Component {
|
||||
const {children, activeTab, cStyle = 'base'} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ul className={`${styles.base} ${cStyle ? styles[cStyle] : ''}`}>
|
||||
<ul className={`${styles.base} ${cStyle ? styles[cStyle] : ''} ${this.props.className}`}>
|
||||
{React.Children.toArray(children)
|
||||
.filter((child) => !child.props.restricted)
|
||||
.map((child, tabId) =>
|
||||
|
||||
+1
-1
@@ -92,11 +92,11 @@
|
||||
"minimist": "^1.2.0",
|
||||
"mongoose": "^4.9.8",
|
||||
"morgan": "^1.8.1",
|
||||
"ms": "^2.0.0",
|
||||
"natural": "^0.5.0",
|
||||
"node-emoji": "^1.5.1",
|
||||
"node-fetch": "^1.6.3",
|
||||
"nodemailer": "^2.6.4",
|
||||
"parse-duration": "^0.1.1",
|
||||
"passport": "^0.3.2",
|
||||
"passport-jwt": "^2.2.1",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
+21
-4
@@ -10,6 +10,7 @@ const uuid = require('uuid');
|
||||
const debug = require('debug')('talk:passport');
|
||||
const {createClient} = require('./redis');
|
||||
const bowser = require('bowser');
|
||||
const ms = require('ms');
|
||||
|
||||
// Create a redis client to use for authentication.
|
||||
const client = createClient();
|
||||
@@ -39,7 +40,7 @@ const SetTokenForSafari = (req, res, token) => {
|
||||
if (browser.ios || browser.safari) {
|
||||
res.cookie('authorization', token, {
|
||||
httpOnly: true,
|
||||
expires: new Date(Date.now() + 900000)
|
||||
expires: new Date(Date.now() + ms(JWT_EXPIRY))
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -172,6 +173,7 @@ const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => {
|
||||
});
|
||||
});
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const JwtStrategy = require('passport-jwt').Strategy;
|
||||
const ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
|
||||
@@ -185,6 +187,19 @@ let cookieExtractor = function(req) {
|
||||
return token;
|
||||
};
|
||||
|
||||
// Override the JwtVerifier method on the JwtStrategy so we can pack the
|
||||
// original token into the payload.
|
||||
JwtStrategy.JwtVerifier = (token, secretOrKey, options, callback) => {
|
||||
return jwt.verify(token, secretOrKey, options, (err, jwt) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Attach the original token onto the payload.
|
||||
return callback(false, {token, jwt});
|
||||
});
|
||||
};
|
||||
|
||||
// Extract the JWT from the 'Authorization' header with the 'Bearer' scheme.
|
||||
passport.use(new JwtStrategy({
|
||||
|
||||
@@ -207,10 +222,10 @@ passport.use(new JwtStrategy({
|
||||
// Enable only the HS256 algorithm.
|
||||
algorithms: ['HS256'],
|
||||
|
||||
// Pass the request objecto back to the callback so we can attach the JWT to
|
||||
// Pass the request object back to the callback so we can attach the JWT to
|
||||
// it.
|
||||
passReqToCallback: true
|
||||
}, async (req, jwt, done) => {
|
||||
}, async (req, {token, jwt}, done) => {
|
||||
|
||||
// Load the user from the environment, because we just got a user from the
|
||||
// header.
|
||||
@@ -219,7 +234,9 @@ passport.use(new JwtStrategy({
|
||||
// Check to see if the token has been revoked
|
||||
await CheckBlacklisted(jwt);
|
||||
|
||||
let user = await UsersService.findById(jwt.sub);
|
||||
// Try to get the user from the database or crack it from the token and
|
||||
// plugin integrations.
|
||||
let user = await UsersService.findOrCreateByIDToken(jwt.sub, {token, jwt});
|
||||
|
||||
// Attach the JWT to the request.
|
||||
req.jwt = jwt;
|
||||
|
||||
@@ -9,6 +9,7 @@ const {
|
||||
JWT_SECRET,
|
||||
ROOT_URL
|
||||
} = require('../config');
|
||||
const debug = require('debug')('talk:services:users');
|
||||
|
||||
const redis = require('./redis');
|
||||
const redisClient = redis.createClient();
|
||||
@@ -526,6 +527,29 @@ module.exports = class UsersService {
|
||||
return UserModel.findOne({id});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} id the id of the current user
|
||||
* @param {Object} token a jwt token used to sign in the user
|
||||
*/
|
||||
static async findOrCreateByIDToken(id, token) {
|
||||
|
||||
// Try to get the user.
|
||||
let user = await UserModel.findOne({
|
||||
id
|
||||
});
|
||||
|
||||
// If the user was not found, try to look it up.
|
||||
if (user === null) {
|
||||
|
||||
// If the user wasn't found, it will return null and the variable will be
|
||||
// unchanged.
|
||||
user = await lookupUserNotFound(token);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds users in an array of ids.
|
||||
* @param {Array} ids array of user identifiers (uuid)
|
||||
@@ -903,3 +927,25 @@ module.exports = class UsersService {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Extract all the tokenUserNotFound plugins so we can integrate with other
|
||||
// providers.
|
||||
const tokenUserNotFoundHooks = require('./plugins')
|
||||
.get('server', 'tokenUserNotFound')
|
||||
.map(({plugin, tokenUserNotFound}) => {
|
||||
debug(`added plugin '${plugin.name}' to tokenUserNotFound hooks`);
|
||||
|
||||
return tokenUserNotFound;
|
||||
});
|
||||
|
||||
// Provide a function that
|
||||
const lookupUserNotFound = async (token) => {
|
||||
for (let hook of tokenUserNotFoundHooks) {
|
||||
let user = await hook(token);
|
||||
if (user !== null && typeof user !== 'undefined') {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -5365,6 +5365,10 @@ ms@0.7.3, ms@^0.7.1:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
|
||||
|
||||
ms@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
muri@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/muri/-/muri-1.2.1.tgz#ec7ea5ce6ca6a523eb1ab35bacda5fa816c9aa3c"
|
||||
@@ -5861,10 +5865,6 @@ parse-asn1@^5.0.0:
|
||||
evp_bytestokey "^1.0.0"
|
||||
pbkdf2 "^3.0.3"
|
||||
|
||||
parse-duration@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-0.1.1.tgz#13114ddc9891c1ecd280036244554de43647a226"
|
||||
|
||||
parse-glob@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
|
||||
|
||||
Reference in New Issue
Block a user