Skip to content

Commit

Permalink
Merge pull request #386 from DemocracyLab/error_500_logging
Browse files Browse the repository at this point in the history
Add Error page with custom message for OAuth signup failure
  • Loading branch information
marlonkeating authored Jun 26, 2020
2 parents 5af95ac + b3f25a3 commit 14a6a19
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 2 deletions.
5 changes: 5 additions & 0 deletions civictechprojects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
from django.conf.urls import url
from django.views.generic import TemplateView
from django.contrib.sitemaps.views import sitemap
from common.helpers.error_handlers import handle500
from .sitemaps import ProjectSitemap, SectionSitemap


from . import views

# Set custom error handler
handler500 = handle500

urlpatterns = [

url(
Expand Down
48 changes: 48 additions & 0 deletions common/components/controllers/ErrorController.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @flow

import React from 'react';
import type {Dictionary} from "../types/Generics.jsx";
import urlHelper from "../utils/url.js";

type ErrorArgs = {|
errorType: string
|}

type State = {|
errorArgs: ErrorArgs
|};

const ErrorMessagesByType: Dictionary<(ErrorArgs) => string> = {
MissingOAuthFieldError: (errorArgs: ErrorArgs) => {
const missingFields: string = decodeURI(errorArgs.missing_fields);
return `Sign up failed, as your account is missing the following fields: ${missingFields}. ` +
`Please update your ${errorArgs.provider} profile with this information or use another method to sign up for DemocracyLab. Thank you!`;
}
};

function getErrorMessage(errorArgs: ErrorArgs): string {
if(errorArgs.errorType in ErrorMessagesByType) {
return ErrorMessagesByType[errorArgs.errorType](errorArgs);
} else {
return "Error";
}
}

class ErrorController extends React.Component<{||}, State> {
constructor(): void {
super();
this.state = {
errorArgs: urlHelper.getSectionArgs().args
};
}

render(): React$Node {
return (
<div>
{getErrorMessage(this.state.errorArgs)}
</div>
);
}
}

export default ErrorController;
3 changes: 3 additions & 0 deletions common/components/controllers/SectionController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import CreateEventController from './CreateEventController.jsx';
import MyGroupsController from './MyGroupsController.jsx';
import LiveEventController from "./LiveEventController.jsx";
import AboutEventController from "./AboutEventController.jsx";
import ErrorController from "./ErrorController.jsx";

type State = {|
section: SectionType,
Expand Down Expand Up @@ -110,6 +111,8 @@ class SectionController extends React.Component<{||}, State> {
return <AboutEventController/>;
case Section.LiveEvent:
return <LiveEventController />;
case Section.Error:
return <ErrorController />;
default:
return <div>Section not yet implemented: {this.state.section}</div>
}
Expand Down
3 changes: 2 additions & 1 deletion common/components/enums/Section.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const Section = {
CreateGroup: 'CreateGroup',
CreateEvent: 'CreateEvent',
AboutEvent: 'AboutEvent',
LiveEvent: 'LiveEvent'
LiveEvent: 'LiveEvent',
Error: 'Error'
};

export type SectionType = $Keys<typeof Section>;
Expand Down
1 change: 1 addition & 0 deletions common/helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ class FrontEndSection(Enum):
EditProfile = 'EditProfile'
ThankYou = 'ThankYou'
EmailVerified = 'EmailVerified'
Error = 'Error'
31 changes: 31 additions & 0 deletions common/helpers/error_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sys
from django.shortcuts import redirect
from common.helpers.constants import FrontEndSection
from common.helpers.dictionaries import merge_dicts
from common.helpers.front_end import section_url


class ReportableError(Exception):
"""Exception raised that needs to be logged and sent to a front end error page
Attributes:
message -- explanation of the error to be reported in the logs
front_end_args -- arguments to be surfaced on the front end error page
"""

def __init__(self, message, front_end_args):
self.message = message
self.front_end_args = front_end_args or {}


def handle500(request):
exception_type, exception, traceback = sys.exc_info()
if isinstance(exception, ReportableError):
# Log message
print("Error(500): " + exception.message)
error_args = merge_dicts(exception.front_end_args, {'errorType': type(exception).__name__})
# Redirect to Error page
return redirect(section_url(FrontEndSection.Error, error_args))
else:
return redirect(section_url(FrontEndSection.Error))

4 changes: 4 additions & 0 deletions democracylab/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView
from common.helpers.error_handlers import handle500

from . import views

# Set custom error handler
handler500 = handle500

urlpatterns = [
url(r'^accounts/', include('oauth2.providers.github.urls')),
url(r'^accounts/', include('oauth2.providers.google.urls')),
Expand Down
23 changes: 22 additions & 1 deletion oauth2/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@
from allauth.account.signals import user_logged_in
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.dispatch import receiver
from common.helpers.error_handlers import ReportableError
from civictechprojects.models import ProjectFile, FileCategory
from democracylab.models import Contributor
from django.contrib.auth.models import User
from django.utils import timezone
import simplejson as json


class MissingOAuthFieldError(ReportableError):
"""Exception raised when required fields are not returned from OAuth
Attributes:
missing_fields -- description of missing fields
message -- explanation of the error to be reported in the logs
"""

def __init__(self, message, provider, missing_fields):
super().__init__(message, {'provider': provider, 'missing_fields': missing_fields})


class SocialAccountAdapter(DefaultSocialAccountAdapter):
Expand All @@ -35,13 +49,20 @@ def pre_social_login(self, request, sociallogin):
raising an ImmediateHttpResponse
"""
# standardizing fields across different providers
data = sociallogin.account.get_provider().extract_common_fields(
provider = sociallogin.account.get_provider()
data = provider.extract_common_fields(
sociallogin.account.extra_data)

full_name = data.get('name')
first_name = data.get('first_name')
last_name = data.get('last_name')

if full_name is None and first_name is None:
missing_fields = ['name', 'first_name', 'last_name']
msg = 'Social login Failed for {provider}. Missing fields: {fields}'\
.format(provider=provider.name, fields=missing_fields)
raise MissingOAuthFieldError(msg, provider.name, "Full Name")

sociallogin.user.first_name = first_name or full_name.split()[0]
sociallogin.user.last_name = last_name or ' '.join(full_name.split()[1:])
# Set username to lowercase email
Expand Down

0 comments on commit 14a6a19

Please sign in to comment.