Skip to content

Commit

Permalink
Merge requires_login and requires_login_no_message
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Jul 13, 2023
1 parent def88f6 commit f7ca62c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 40 deletions.
8 changes: 2 additions & 6 deletions funnel/views/api/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
from ...registry import resource_registry
from ...typing import ReturnView
from ...utils import abort_null, make_redirect_url
from ..login_session import (
reload_for_cookies,
requires_client_login,
requires_login_no_message,
)
from ..login_session import reload_for_cookies, requires_client_login, requires_login
from .resource import get_userinfo


Expand Down Expand Up @@ -175,7 +171,7 @@ def oauth_auth_error(

@app.route('/api/1/auth', methods=['GET', 'POST'])
@reload_for_cookies
@requires_login_no_message
@requires_login('')
def oauth_authorize() -> ReturnView:
"""Provide authorization endpoint for OAuth2 server."""
form = forms.Form()
Expand Down
4 changes: 2 additions & 2 deletions funnel/views/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def account_merge() -> ReturnView:
# 3. Redirect user to `app` /login/hasjob?code={code}

# 2. `app` /login/hasjob does:
# 1. Ask user to login if required (@requires_login_no_message)
# 1. Ask user to login if required (@requires_login(''))
# 2. Verify signature of code
# 3. Create a timestamped token using (nonce, user_session.buid)
# 4. Redirect user to `hasjobapp` /login/callback?token={token}
Expand Down Expand Up @@ -703,7 +703,7 @@ def hasjob_login(cookietest: bool = False) -> ReturnView:

# @app.route('/login/hasjob')
# @reload_for_cookies
# @requires_login_no_message # 1. Ensure user login
# @requires_login('') # 1. Ensure user login
# @requestargs('code')
# def login_hasjob(code):
# """Process a request for login initiated from Hasjob."""
Expand Down
83 changes: 52 additions & 31 deletions funnel/views/login_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from datetime import timedelta
from functools import wraps
from typing import Callable, Optional, Type, Union
from typing import Callable, Optional, Type, Union, overload

import geoip2.errors
import itsdangerous
Expand All @@ -23,7 +23,7 @@
)
from furl import furl

from baseframe import _, statsd
from baseframe import _, __, statsd
from baseframe.forms import render_form
from coaster.auth import add_auth_attribute, current_auth, request_has_auth
from coaster.utils import utcnow
Expand Down Expand Up @@ -440,7 +440,7 @@ def decorator(f: Callable[P, T]) -> Callable[P, Union[T, ReturnResponse]]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Union[T, ReturnResponse]:
"""Validate user rights in a view."""
if not current_auth.is_authenticated:
flash(_("You need to be logged in for that page"), 'info')
flash(_("Confirm your phone number to continue"), 'info')
return render_redirect(
url_for('login', next=get_current_url()),
302 if request.method == 'GET' else 303,
Expand All @@ -460,43 +460,64 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Union[T, ReturnResponse]:
return decorator


def requires_login(f: Callable[P, T]) -> Callable[P, Union[T, ReturnResponse]]:
"""Decorate a view to require login."""
@overload
def requires_login(
__p: str,
) -> Callable[[Callable[P, T]], Callable[P, Union[T, ReturnResponse]]]:
...

@wraps(f)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Union[T, ReturnResponse]:
add_auth_attribute('login_required', True)
if not current_auth.is_authenticated:
flash(_("You need to be logged in for that page"), 'info')
return render_redirect(
url_for('login', next=get_current_url()),
302 if request.method == 'GET' else 303,
)
return f(*args, **kwargs)

return wrapper
@overload
def requires_login(__p: Callable[P, T]) -> Callable[P, Union[T, ReturnResponse]]:
...


def requires_login_no_message(
f: Callable[P, T]
) -> Callable[P, Union[T, ReturnResponse]]:
def requires_login(
__p: Union[str, Callable[P, T]]
) -> Union[
Callable[[Callable[P, T]], Callable[P, Union[T, ReturnResponse]]],
Callable[P, Union[T, ReturnResponse]],
]:
"""
Decorate a view to require login, without displaying a friendly message.
Decorate a view to require login, with a customisable message.
Used on views where the user is informed in advance that login is required.
Usage::
@requires_login
def view_requiring_login():
...
@requires_login(__("Message to be shown"))
def view_requiring_login_with_custom_message():
...
@requires_login('')
def view_requiring_login_with_no_message():
...
"""
if callable(__p):
message = __("You need to be logged in for that page")
else:
message = __p

@wraps(f)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Union[T, ReturnResponse]:
add_auth_attribute('login_required', True)
if not current_auth.is_authenticated:
return render_redirect(
url_for('login', next=get_current_url()),
302 if request.method == 'GET' else 303,
)
return f(*args, **kwargs)
def decorator(f: Callable[P, T]) -> Callable[P, Union[T, ReturnResponse]]:
@wraps(f)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Union[T, ReturnResponse]:
add_auth_attribute('login_required', True)
if not current_auth.is_authenticated:
if message: # Setting an empty message will disable it
flash(message, 'info')
return render_redirect(
url_for('login', next=get_current_url()),
302 if request.method == 'GET' else 303,
)
return f(*args, **kwargs)

return wrapper
return wrapper

if callable(__p):
return decorator(__p)
return decorator


def save_sudo_preference_context() -> None:
Expand Down
4 changes: 3 additions & 1 deletion funnel/views/notification_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class AccountNotificationView(ClassView):
current_section = 'account'

@route('', endpoint='notification_preferences')
@requires_login
@requires_login(
__("Your phone number or email address is required to change your preferences")
)
@render_with('notification_preferences.html.jinja2')
def notification_preferences(self) -> ReturnRenderWith:
"""View for notification preferences."""
Expand Down

0 comments on commit f7ca62c

Please sign in to comment.