Skip to content

Commit 14a6a19

Browse files
Merge pull request #386 from DemocracyLab/error_500_logging
Add Error page with custom message for OAuth signup failure
2 parents 5af95ac + b3f25a3 commit 14a6a19

File tree

8 files changed

+116
-2
lines changed

8 files changed

+116
-2
lines changed

civictechprojects/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616
from django.conf.urls import url
1717
from django.views.generic import TemplateView
1818
from django.contrib.sitemaps.views import sitemap
19+
from common.helpers.error_handlers import handle500
1920
from .sitemaps import ProjectSitemap, SectionSitemap
2021

2122

2223
from . import views
24+
25+
# Set custom error handler
26+
handler500 = handle500
27+
2328
urlpatterns = [
2429

2530
url(
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// @flow
2+
3+
import React from 'react';
4+
import type {Dictionary} from "../types/Generics.jsx";
5+
import urlHelper from "../utils/url.js";
6+
7+
type ErrorArgs = {|
8+
errorType: string
9+
|}
10+
11+
type State = {|
12+
errorArgs: ErrorArgs
13+
|};
14+
15+
const ErrorMessagesByType: Dictionary<(ErrorArgs) => string> = {
16+
MissingOAuthFieldError: (errorArgs: ErrorArgs) => {
17+
const missingFields: string = decodeURI(errorArgs.missing_fields);
18+
return `Sign up failed, as your account is missing the following fields: ${missingFields}. ` +
19+
`Please update your ${errorArgs.provider} profile with this information or use another method to sign up for DemocracyLab. Thank you!`;
20+
}
21+
};
22+
23+
function getErrorMessage(errorArgs: ErrorArgs): string {
24+
if(errorArgs.errorType in ErrorMessagesByType) {
25+
return ErrorMessagesByType[errorArgs.errorType](errorArgs);
26+
} else {
27+
return "Error";
28+
}
29+
}
30+
31+
class ErrorController extends React.Component<{||}, State> {
32+
constructor(): void {
33+
super();
34+
this.state = {
35+
errorArgs: urlHelper.getSectionArgs().args
36+
};
37+
}
38+
39+
render(): React$Node {
40+
return (
41+
<div>
42+
{getErrorMessage(this.state.errorArgs)}
43+
</div>
44+
);
45+
}
46+
}
47+
48+
export default ErrorController;

common/components/controllers/SectionController.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import CreateEventController from './CreateEventController.jsx';
3333
import MyGroupsController from './MyGroupsController.jsx';
3434
import LiveEventController from "./LiveEventController.jsx";
3535
import AboutEventController from "./AboutEventController.jsx";
36+
import ErrorController from "./ErrorController.jsx";
3637

3738
type State = {|
3839
section: SectionType,
@@ -110,6 +111,8 @@ class SectionController extends React.Component<{||}, State> {
110111
return <AboutEventController/>;
111112
case Section.LiveEvent:
112113
return <LiveEventController />;
114+
case Section.Error:
115+
return <ErrorController />;
113116
default:
114117
return <div>Section not yet implemented: {this.state.section}</div>
115118
}

common/components/enums/Section.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const Section = {
2626
CreateGroup: 'CreateGroup',
2727
CreateEvent: 'CreateEvent',
2828
AboutEvent: 'AboutEvent',
29-
LiveEvent: 'LiveEvent'
29+
LiveEvent: 'LiveEvent',
30+
Error: 'Error'
3031
};
3132

3233
export type SectionType = $Keys<typeof Section>;

common/helpers/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ class FrontEndSection(Enum):
3535
EditProfile = 'EditProfile'
3636
ThankYou = 'ThankYou'
3737
EmailVerified = 'EmailVerified'
38+
Error = 'Error'

common/helpers/error_handlers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import sys
2+
from django.shortcuts import redirect
3+
from common.helpers.constants import FrontEndSection
4+
from common.helpers.dictionaries import merge_dicts
5+
from common.helpers.front_end import section_url
6+
7+
8+
class ReportableError(Exception):
9+
"""Exception raised that needs to be logged and sent to a front end error page
10+
11+
Attributes:
12+
message -- explanation of the error to be reported in the logs
13+
front_end_args -- arguments to be surfaced on the front end error page
14+
"""
15+
16+
def __init__(self, message, front_end_args):
17+
self.message = message
18+
self.front_end_args = front_end_args or {}
19+
20+
21+
def handle500(request):
22+
exception_type, exception, traceback = sys.exc_info()
23+
if isinstance(exception, ReportableError):
24+
# Log message
25+
print("Error(500): " + exception.message)
26+
error_args = merge_dicts(exception.front_end_args, {'errorType': type(exception).__name__})
27+
# Redirect to Error page
28+
return redirect(section_url(FrontEndSection.Error, error_args))
29+
else:
30+
return redirect(section_url(FrontEndSection.Error))
31+

democracylab/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
from django.contrib import admin
1818
from django.contrib.auth import views as auth_views
1919
from django.views.generic.base import RedirectView
20+
from common.helpers.error_handlers import handle500
2021

2122
from . import views
2223

24+
# Set custom error handler
25+
handler500 = handle500
26+
2327
urlpatterns = [
2428
url(r'^accounts/', include('oauth2.providers.github.urls')),
2529
url(r'^accounts/', include('oauth2.providers.google.urls')),

oauth2/adapter.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,24 @@
55
from allauth.account.signals import user_logged_in
66
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
77
from django.dispatch import receiver
8+
from common.helpers.error_handlers import ReportableError
89
from civictechprojects.models import ProjectFile, FileCategory
910
from democracylab.models import Contributor
1011
from django.contrib.auth.models import User
1112
from django.utils import timezone
13+
import simplejson as json
14+
15+
16+
class MissingOAuthFieldError(ReportableError):
17+
"""Exception raised when required fields are not returned from OAuth
18+
19+
Attributes:
20+
missing_fields -- description of missing fields
21+
message -- explanation of the error to be reported in the logs
22+
"""
23+
24+
def __init__(self, message, provider, missing_fields):
25+
super().__init__(message, {'provider': provider, 'missing_fields': missing_fields})
1226

1327

1428
class SocialAccountAdapter(DefaultSocialAccountAdapter):
@@ -35,13 +49,20 @@ def pre_social_login(self, request, sociallogin):
3549
raising an ImmediateHttpResponse
3650
"""
3751
# standardizing fields across different providers
38-
data = sociallogin.account.get_provider().extract_common_fields(
52+
provider = sociallogin.account.get_provider()
53+
data = provider.extract_common_fields(
3954
sociallogin.account.extra_data)
4055

4156
full_name = data.get('name')
4257
first_name = data.get('first_name')
4358
last_name = data.get('last_name')
4459

60+
if full_name is None and first_name is None:
61+
missing_fields = ['name', 'first_name', 'last_name']
62+
msg = 'Social login Failed for {provider}. Missing fields: {fields}'\
63+
.format(provider=provider.name, fields=missing_fields)
64+
raise MissingOAuthFieldError(msg, provider.name, "Full Name")
65+
4566
sociallogin.user.first_name = first_name or full_name.split()[0]
4667
sociallogin.user.last_name = last_name or ' '.join(full_name.split()[1:])
4768
# Set username to lowercase email

0 commit comments

Comments
 (0)