Skip to content

Commit 950f5d3

Browse files
committed
Authenticate seemlessly and handle error better
1 parent e02b82e commit 950f5d3

File tree

10 files changed

+89
-67
lines changed

10 files changed

+89
-67
lines changed

src/backend/common/error.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
class ApplicationError extends Error {
1+
class ClientError extends Error {
22
toJSON() {
3-
return { message: this.message };
3+
return { name: this.constructor.name, message: this.message };
44
}
55
}
66

7-
class NotFoundError extends ApplicationError {
7+
class NotFoundError extends ClientError {
88
}
99

10-
class PermissionError extends ApplicationError {
10+
class ForbiddenError extends ClientError {
1111
}
1212

13-
class AuthorizationError extends ApplicationError {
13+
class UnauthorizedError extends ClientError {
1414
}
1515

16-
class CompileError extends ApplicationError {
16+
class CompileError extends ClientError {
1717
}
1818

19-
class RuntimeError extends ApplicationError {
19+
class RuntimeError extends ClientError {
2020
}
2121

2222
export {
23-
ApplicationError,
23+
ClientError,
2424
NotFoundError,
25-
PermissionError,
26-
AuthorizationError,
25+
ForbiddenError,
26+
UnauthorizedError,
2727
CompileError,
2828
RuntimeError,
2929
};

src/backend/controllers/auth.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ const response = (req, res, next) => {
1212
const { code } = req.query;
1313

1414
GitHubApi.getAccessToken(code).then(({ access_token }) => {
15-
res.cookie('access_token', access_token);
16-
res.redirect('/');
15+
res.send(`<script>window.opener.signIn('${access_token}');window.close();</script>`);
1716
}).catch(next);
1817
};
1918

2019
const destroy = (req, res, next) => {
21-
res.clearCookie('access_token');
22-
res.redirect('/');
20+
res.send(`<script>window.opener.signOut();window.close();</script>`);
2321
};
2422

2523
router.route('/request')

src/backend/index.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import morgan from 'morgan';
33
import cookieParser from 'cookie-parser';
44
import bodyParser from 'body-parser';
55
import * as controllers from '/controllers';
6-
import { AuthorizationError, CompileError, NotFoundError, PermissionError, RuntimeError } from '/common/error';
6+
import {
7+
ClientError,
8+
CompileError,
9+
ForbiddenError,
10+
NotFoundError,
11+
RuntimeError,
12+
UnauthorizedError,
13+
} from '/common/error';
714

815
const app = express();
916
app.use(morgan('tiny'));
@@ -14,19 +21,17 @@ Object.keys(controllers).forEach(key => app.use(`/${key}`, controllers[key]));
1421
app.use((req, res, next) => next(new NotFoundError()));
1522
app.use((err, req, res, next) => {
1623
const statusMap = [
17-
[AuthorizationError, 401],
18-
[PermissionError, 403],
24+
[UnauthorizedError, 401],
25+
[ForbiddenError, 403],
1926
[NotFoundError, 404],
2027
[CompileError, 422],
2128
[RuntimeError, 422],
29+
[ClientError, 400],
2230
[Error, 500],
2331
];
2432
const [, status] = statusMap.find(([Error]) => err instanceof Error);
2533
res.status(status);
26-
res.json({
27-
status,
28-
err,
29-
});
34+
res.json(err);
3035
console.error(err);
3136
});
3237

src/frontend/apis/index.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import Promise from 'bluebird';
22
import axios from 'axios';
33

4-
axios.interceptors.response.use(response => {
5-
return response.data;
6-
}, error => {
7-
return Promise.reject(error.response.data);
8-
});
4+
axios.interceptors.response.use(
5+
response => response.data,
6+
error => Promise.reject(error.response.data),
7+
);
98

109
const request = (url, process) => {
1110
const tokens = url.split('/');
@@ -59,7 +58,7 @@ const CategoryApi = {
5958
};
6059

6160
const GitHubApi = {
62-
auth: token => Promise.resolve(axios.defaults.headers.common['Authorization'] = `token ${token}`),
61+
auth: token => Promise.resolve(axios.defaults.headers.common['Authorization'] = token && `token ${token}`),
6362
getUser: GET('https://api.github.com/user'),
6463
listGists: GET('https://api.github.com/gists'),
6564
createGist: POST('https://api.github.com/gists'),
@@ -74,7 +73,7 @@ const TracerApi = {
7473
if (jsWorker) jsWorker.terminate();
7574
jsWorker = new Worker('/api/tracers/js');
7675
jsWorker.onmessage = e => resolve(e.data);
77-
jsWorker.onerror = err => reject({ status: 422, err });
76+
jsWorker.onerror = reject;
7877
jsWorker.postMessage(code);
7978
}),
8079
java: POST('/tracers/java'),

src/frontend/components/App/index.jsx

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import Cookies from 'js-cookie';
23
import { connect } from 'react-redux';
34
import Promise from 'bluebird';
45
import AutosizeInput from 'react-input-autosize';
@@ -40,26 +41,25 @@ class App extends React.Component {
4041
}
4142

4243
componentDidMount() {
44+
window.signIn = this.signIn.bind(this);
45+
window.signOut = this.signOut.bind(this);
46+
4347
this.loadAlgorithm(this.props.match.params);
4448

45-
const { accessToken } = this.props.env;
46-
if (accessToken) {
47-
GitHubApi.auth(accessToken)
48-
.then(() => GitHubApi.getUser())
49-
.then(user => {
50-
const { login, avatar_url } = user;
51-
this.props.setUser({ login, avatar_url });
52-
})
53-
.then(() => this.loadScratchPapers());
54-
}
49+
const accessToken = Cookies.get('access_token');
50+
if (accessToken) this.signIn(accessToken);
5551

5652
CategoryApi.getCategories()
57-
.then(({ categories }) => this.props.setCategories(categories));
53+
.then(({ categories }) => this.props.setCategories(categories))
54+
.catch(this.props.showErrorToast);
5855

59-
tracerManager.setOnError(error => this.props.showErrorToast(error.message));
56+
tracerManager.setOnError(error => this.props.showErrorToast({ name: error.name, message: error.message }));
6057
}
6158

6259
componentWillUnmount() {
60+
delete window.signIn;
61+
delete window.signOut;
62+
6363
tracerManager.setOnError(null);
6464
}
6565

@@ -83,6 +83,25 @@ class App extends React.Component {
8383
}
8484
}
8585

86+
signIn(accessToken) {
87+
Cookies.set('access_token', accessToken);
88+
GitHubApi.auth(accessToken)
89+
.then(() => GitHubApi.getUser())
90+
.then(user => {
91+
const { login, avatar_url } = user;
92+
this.props.setUser({ login, avatar_url });
93+
})
94+
.then(() => this.loadScratchPapers())
95+
.catch(() => this.signOut());
96+
}
97+
98+
signOut() {
99+
Cookies.remove('access_token');
100+
GitHubApi.auth(undefined)
101+
.then(() => this.props.setUser(undefined))
102+
.then(() => this.props.setScratchPapers([]));
103+
}
104+
86105
loadScratchPapers() {
87106
const per_page = 100;
88107
const paginateGists = (page = 1, scratchPapers = []) => GitHubApi.listGists({
@@ -101,7 +120,9 @@ class App extends React.Component {
101120
return paginateGists(page + 1, scratchPapers);
102121
}
103122
});
104-
return paginateGists().then(scratchPapers => this.props.setScratchPapers(scratchPapers));
123+
return paginateGists()
124+
.then(scratchPapers => this.props.setScratchPapers(scratchPapers))
125+
.catch(this.props.showErrorToast);
105126
}
106127

107128
loadAlgorithm({ categoryKey, algorithmKey, gistId }) {

src/frontend/components/Header/index.jsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@ class Header extends React.Component {
8484
savePromise
8585
.then(refineGist)
8686
.then(algorithm => this.props.setCurrent(categoryKey, algorithmKey, algorithm.gistId, algorithm.titles, algorithm.files))
87-
.then(this.props.loadScratchPapers);
87+
.then(this.props.loadScratchPapers)
88+
.catch(this.props.showErrorToast);
8889
}
8990

9091
deleteGist() {
9192
const { gistId } = this.props.current;
9293
const deletePromise = gistId === 'new' ? Promise.resolve() : GitHubApi.deleteGist(gistId);
93-
deletePromise.then(() => this.props.loadAlgorithm({})).then(this.props.loadScratchPapers);
94+
deletePromise
95+
.then(() => this.props.loadAlgorithm({}))
96+
.then(this.props.loadScratchPapers)
97+
.catch(this.props.showErrorToast);
9498
}
9599

96100
isGistSaved() {
@@ -129,7 +133,8 @@ class Header extends React.Component {
129133
<div className={styles.section}>
130134
<Button icon={faSave} primary disabled={!gistId || this.isGistSaved()}
131135
onClick={() => this.saveGist()}>Save</Button>
132-
<Button icon={faTrashAlt} primary disabled={!gistId} onClick={() => this.deleteGist()} confirmNeeded>Delete</Button>
136+
<Button icon={faTrashAlt} primary disabled={!gistId} onClick={() => this.deleteGist()}
137+
confirmNeeded>Delete</Button>
133138
<Button icon={faShare} primary disabled={gistId === 'new'} onClick={() => this.shareLink()}>Share</Button>
134139
<Button icon={faExpandArrowsAlt} primary
135140
onClick={() => this.handleClickFullScreen()}>Fullscreen</Button>
@@ -142,10 +147,10 @@ class Header extends React.Component {
142147
<Button className={styles.btn_dropdown} icon={user.avatar_url}>
143148
{user.login}
144149
<div className={styles.dropdown}>
145-
<ListItem href="/api/auth/destroy" label="Sign Out" />
150+
<ListItem label="Sign Out" href="/api/auth/destroy" rel="nofollow" />
146151
</div>
147152
</Button> :
148-
<Button icon={faGithub} primary href="/api/auth/request">
153+
<Button icon={faGithub} primary href="/api/auth/request" rel="nofollow">
149154
<Ellipsis>Sign In</Ellipsis>
150155
</Button>
151156
}

src/frontend/components/Navigator/index.jsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,16 @@ class Navigator extends React.Component {
108108
}
109109
</div>
110110
<div className={styles.footer}>
111-
{
112-
user ?
113-
<ExpandableListItem icon={faCode} label="Scratch Paper" onClick={() => this.toggleScratchPaper()}
114-
opened={scratchPaperOpened}>
115-
<ListItem indent label="New ..." onClick={() => loadAlgorithm({ gistId: 'new' })} />
116-
{
117-
scratchPapers.map(scratchPaper => (
118-
<ListItem indent key={scratchPaper.key} selected={scratchPaper.key === gistId}
119-
onClick={() => loadAlgorithm({ gistId: scratchPaper.key })} label={scratchPaper.name} />
120-
))
121-
}
122-
</ExpandableListItem> :
123-
<ListItem icon={faCode} label="Scratch Paper"
124-
onClick={() => this.props.showSuccessToast('Sign In Required')} />
125-
}
111+
<ExpandableListItem icon={faCode} label="Scratch Paper" onClick={() => this.toggleScratchPaper()}
112+
opened={scratchPaperOpened}>
113+
<ListItem indent label="New ..." onClick={() => loadAlgorithm({ gistId: 'new' })} />
114+
{
115+
scratchPapers.map(scratchPaper => (
116+
<ListItem indent key={scratchPaper.key} selected={scratchPaper.key === gistId}
117+
onClick={() => loadAlgorithm({ gistId: scratchPaper.key })} label={scratchPaper.name} />
118+
))
119+
}
120+
</ExpandableListItem>
126121
<ListItem icon={faBook} label="Tracers API"
127122
href="https://github.com/algorithm-visualizer/tracers/wiki" />
128123
<ListItem icon={faGithub} label="Fork me on GitHub"

src/frontend/core/tracerManager.jsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,7 @@ class TracerManager {
156156
const { name, content } = this.file;
157157
const ext = extension(name);
158158
if (ext in TracerApi) {
159-
return TracerApi[ext]({ code: content })
160-
.catch(e => {
161-
throw e.err;
162-
});
159+
return TracerApi[ext]({ code: content });
163160
} else {
164161
return Promise.reject(new Error('Language Not Supported'));
165162
}

src/frontend/reducers/env.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export const actions = {
1515
};
1616

1717
const defaultState = {
18-
accessToken: Cookies.get('access_token'),
1918
ext: Cookies.get('ext') || 'js',
2019
user: undefined,
2120
};

src/frontend/reducers/toast.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import uuid from 'uuid';
44
const prefix = 'TOAST';
55

66
const showSuccessToast = createAction(`${prefix}/SHOW_SUCCESS_TOAST`, message => ({ type: 'success', message }));
7-
const showErrorToast = createAction(`${prefix}/SHOW_ERROR_TOAST`, message => ({ type: 'error', message }));
7+
const showErrorToast = createAction(`${prefix}/SHOW_ERROR_TOAST`, error => ({
8+
type: 'error',
9+
message: [error.name, error.message].filter(v => v).join(': '),
10+
}));
811
const hideToast = createAction(`${prefix}/HIDE_TOAST`, id => ({ id }));
912

1013
export const actions = {

0 commit comments

Comments
 (0)