Skip to content

Commit 3050cd1

Browse files
committed
Improved initial key setup
This commit adds error catching during the initial key handshake which sends the administrator back to the settings page with a (hopefully) helpful error message that helps fix the problem. These messages may need to be adjusted over time, but are certainly better than the current 500 message which can be very unfriendly for people who are just setting up the application. In addition the activation message on the profile page was simplified a bit. Users are only redirected to their profile on login when they are not active.
1 parent 0bad4fc commit 3050cd1

7 files changed

Lines changed: 164 additions & 89 deletions

File tree

src/client/components/Profile.css

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
.Profile {
2-
width: 250px;
3-
margin-top: 20vh;
2+
max-width: 500px;
43
margin-left: auto;
54
margin-right: auto;
5+
display: flex;
6+
flex-flow: row wrap;
7+
justify-content: space-between;
68
}
79

8-
.Profile .TwitterInfo {
9-
margin-bottom: 30px;
10-
}
11-
12-
.Profile div {
13-
margin-bottom: 10px;
10+
.Profile form {
11+
margin-left: auto;
12+
margin-right: auto;
1413
}
1514

16-
.Inactive {
17-
color: #fff;
18-
margin: 24px 0;
19-
padding: 12px 18px;
20-
overflow: auto;
21-
direction: ltr;
22-
border-radius: 4px;
23-
background-color: #272c34;
15+
.Profile .Inactive {
16+
color: red;
2417
}

src/client/components/Profile.js

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { Component } from 'react'
22
import PropTypes from 'prop-types'
33
import TextField from '@material-ui/core/TextField'
44
import Button from '@material-ui/core/Button'
5-
import Typography from '@material-ui/core/Typography'
65
import style from './Profile.css'
76

87
export default class Profile extends Component {
@@ -14,13 +13,14 @@ export default class Profile extends Component {
1413
disableSave = false
1514
}
1615

16+
const tweetQuota = this.props.user.tweetQuota ? this.props.user.tweetQuota.toLocaleString() : ''
17+
1718
const awaitingActivation = this.props.user.active ? ''
18-
: <div className={style.Inactive}>
19-
<Typography variant="body1" gutterBottom>
19+
: <p className={style.Inactive}>
2020
Your account is awaiting activation. In the meantime, you can explore
21-
and save searches, but saved searches will not keep collecting tweets.
22-
</Typography>
23-
</div>
21+
and save searches, but you will not be able to activate your search and
22+
collect live data from the Twitter stream.
23+
</p>
2424

2525
// Once we have emails in place, change text above to:
2626
// Your account is awaiting activation. The admin has been notified.
@@ -29,49 +29,51 @@ export default class Profile extends Component {
2929
return (
3030
<div className={style.Profile}>
3131

32-
<form>
33-
34-
<div className={style.TwitterInfo}>
35-
<a href={`https://twitter.com/${this.props.user.twitterScreenName}`}>
36-
<img
37-
title={`Linked to Twitter as @${this.props.user.twitterScreenName}`}
38-
src={this.props.user.twitterAvatarUrl} />
39-
</a>
40-
</div>
41-
4232
{awaitingActivation}
4333

44-
<div>
45-
<Typography variant="body1" gutterBottom>
46-
Your max tweet quota: {this.props.user.tweetQuota}
47-
</Typography>
48-
</div>
49-
50-
<div>
51-
<TextField
52-
name="name"
53-
label="Name"
54-
onChange={this.props.updateUserSettings}
55-
value={this.props.user.name} />
56-
</div>
57-
58-
<div>
59-
<TextField
60-
name="email"
61-
label="Email"
62-
onChange={this.props.updateUserSettings}
63-
value={this.props.user.email} />
64-
</div>
65-
66-
<div>
67-
<Button
68-
color="primary"
69-
variant="outlined"
70-
disabled={disableSave}
71-
onClick={this.props.saveAllSettings}>
72-
Save
73-
</Button>
74-
</div>
34+
<form>
35+
36+
<p className={style.TwitterInfo}>
37+
<a href={`https://twitter.com/${this.props.user.twitterScreenName}`}>
38+
<img
39+
title={`Linked to Twitter as @${this.props.user.twitterScreenName}`}
40+
src={this.props.user.twitterAvatarUrl} />
41+
</a>
42+
</p>
43+
44+
<p>
45+
<TextField
46+
name="name"
47+
label="Name"
48+
onChange={this.props.updateUserSettings}
49+
value={this.props.user.name} />
50+
</p>
51+
52+
<p>
53+
<TextField
54+
name="email"
55+
label="Email"
56+
onChange={this.props.updateUserSettings}
57+
value={this.props.user.email} />
58+
</p>
59+
60+
<p>
61+
<TextField
62+
name="quota"
63+
label="Tweet Quota"
64+
disabled={true}
65+
value={tweetQuota || 25000} />
66+
</p>
67+
68+
<p>
69+
<Button
70+
color="primary"
71+
variant="outlined"
72+
disabled={disableSave}
73+
onClick={this.props.saveAllSettings}>
74+
Save
75+
</Button>
76+
</p>
7577

7678
</form>
7779

src/client/components/Settings.css

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,20 @@
1616
}
1717

1818
.Settings .Welcome {
19-
width: 500px;
19+
margin-top: 20px;
20+
}
21+
22+
.Settings .Welcome ol {
23+
list-style-type: decimal;
24+
}
25+
26+
.Settings ol li {
27+
}
28+
29+
.Settings .Error {
30+
background-color: pink;
31+
border: 2px solid grey;
32+
padding: 2px 10px 2px 10px;
2033
}
2134

2235
.SettingsUnder780px input {

src/client/components/Settings.js

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,54 @@ import Button from '@material-ui/core/Button'
77
import LogoUpload from './LogoUpload'
88
import MediaQueryComponent from '../components/MediaQueryComponent'
99

10+
function Error({message, callbackUrl}) {
11+
console.log(message, callbackUrl)
12+
if (message) {
13+
let description = message
14+
if (message.match(/Could not authenticate/)) {
15+
description = (
16+
<div>
17+
<p>Eek, it looks like you have an error with your keys!</p>
18+
<p>
19+
Please make sure you are copying and pasting the
20+
<em>API key</em> and <em>API key secret</em> correctly
21+
from the Twitter Developer Dashboard.
22+
</p>
23+
</div>
24+
)
25+
} else if (message.match(/Desktop applications/)) {
26+
description = (
27+
<div>
28+
<p>Whoops, it looks like 3-Legged oAuth is not enabled.</p>
29+
<p>
30+
Please go to your app page
31+
at <a href="https://developer.twitter.com">developer.twitter.com</a> and
32+
edit your <em>Authentication Settings</em> to enable it.
33+
</p>
34+
</div>
35+
)
36+
} else if (message.match(/Callback URL/)) {
37+
description = (
38+
<div>
39+
<p>Sorry, it looks like the <em>Callback URL</em> has not been set correctly.</p>
40+
<p>Please go to your app page
41+
at <a href="https://developer.twitter.com">developer.twitter.com</a> and
42+
add <em>{callbackUrl}</em> to the <em>Callback URLs</em> in
43+
the <em>Authentication settings</em> section.
44+
</p>
45+
</div>
46+
)
47+
}
48+
return (
49+
<div className={style.Error}>
50+
{description}
51+
</div>
52+
)
53+
} else {
54+
return ''
55+
}
56+
}
57+
1058
export default class Settings extends MediaQueryComponent {
1159

1260
componentWillMount() {
@@ -20,26 +68,33 @@ export default class Settings extends MediaQueryComponent {
2068

2169
render() {
2270

23-
// if there isn't a user provide a welcome message to the first user
24-
// who is presumed to be the admin.
71+
// If there isn't a user provide a welcome message to the first user
72+
// who is presumed to be the admin. When the key handshake with Twitter
73+
// fails the user will be redirected to /settings/ with the error query
74+
// parameter set to the message.
2575

2676
let welcome = ''
2777
if (! this.props.userLoggedIn) {
78+
const q = new URLSearchParams(window.location.search)
79+
const callbackUrl = 'https://demo.docnow.dev/auth/twitter/callback'
80+
const error = <Error message={q.get('error')} callbackUrl={callbackUrl} />
81+
2882
welcome = (
2983
<div className={style.Welcome}>
30-
<br />
3184
<b>Welcome!</b>
3285
<p>
33-
To setup DocNow, you will need to create a
34-
<em>Twitter application</em> and configure DocNow to use
35-
your <em>Consumer Key</em> and <em>Consumer Secret</em> keys
36-
from Twitter.
37-
</p>
38-
<p>
39-
Please visit <a href="https://apps.twitter.com">apps.twitter.com</a> to
40-
to create your application, and let us know if you
41-
have any difficulties at info@docnow.io.
86+
To setup DocNow, you will need to visit the
87+
<a href="https://developer.twitter.com">Twitter Developer Portal</a> and
88+
create a <em>Twitter application</em>. This is required because you will
89+
need to enter your <em>Application key</em> and
90+
<em>Application key secret</em> below.
4291
</p>
92+
<ol>
93+
<li>Set your <em>Description</em> to something that describes your DocNow instance for users who are logging in.</li>
94+
<li>In <em>Authentication Settings</em> please make sure that <em>3-Legged oAuth</em> is enabled.</li>
95+
<li>Set a callback URL of <em>{callbackUrl}</em></li>
96+
</ol>
97+
{error}
4398
</div>
4499
)
45100
}

src/client/containers/App.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
padding: 0;
2929
}
3030

31-
.App li {
31+
.App ul li {
3232
display: inline;
3333
}
3434

src/server/api.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ app.put('/user/:userId', async (req, res) => {
7878

7979
app.get('/settings', async (req, res) => {
8080
const settings = await db.getSettings()
81-
if (! settings || ! req.user) {
82-
if (! req.user || ! req.user.isSuperUser) {
83-
delete settings.appKey
84-
delete settings.appSecret
85-
}
86-
res.json(settings)
81+
// if they aren't logged in or the they're not an admin
82+
// be sure to delete the app key settings!
83+
if (! req.user || (req.user && ! req.user.isSuperUser)) {
84+
delete settings.appKey
85+
delete settings.appSecret
8786
}
87+
res.json(settings)
8888
})
8989

9090
app.put('/settings', async (req, res) => {

src/server/auth.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ const activateKeys = () => {
1616
consumerSecret: settings.appSecret,
1717
callbackURL: '/auth/twitter/callback',
1818
proxy: true,
19-
includeEmail: true
19+
includeEmail: true,
20+
passReqToCallback: true
2021
},
21-
(token, tokenSecret, profile, cb) => {
22+
(req, token, tokenSecret, profile, cb) => {
2223
db.getUserByTwitterUserId(profile.id).then((user) => {
2324
if (! user) {
2425
const newUser = {
@@ -64,12 +65,23 @@ passport.deserializeUser((userId, done) => {
6465
app.use(passport.initialize())
6566
app.use(passport.session())
6667

67-
app.get('/twitter', passport.authenticate('twitter'))
68+
app.get('/twitter', (req, res, next) => {
69+
passport.authenticate('twitter', err => {
70+
if (err) {
71+
return res.redirect(`/settings/?error=${encodeURIComponent(err.message)}`)
72+
}
73+
})(req, res, next)
74+
})
6875

6976
app.get('/twitter/callback',
70-
passport.authenticate('twitter', { failureRedirect: '/login' }),
71-
(req, res) => {
72-
res.redirect('/profile/')
77+
passport.authenticate('twitter', {failureRedirect: '/'}),
78+
async (req, res) => {
79+
const user = await db.getUser(req.user)
80+
if (user.active) {
81+
res.redirect('/')
82+
} else {
83+
res.redirect('/profile/')
84+
}
7385
}
7486
)
7587

0 commit comments

Comments
 (0)