def reset_email(user, kwargs): logout_internal() resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs["secret"]).first() if not resetreq: return render_message(title="Invalid reset link", message=Markup("The reset link you clicked on is invalid.")) if resetreq.created_at < datetime.utcnow() - timedelta(days=1): # Reset code has expired (> 24 hours). Delete it db.session.delete(resetreq) db.session.commit() return render_message(title="Expired reset link", message=Markup("The reset link you clicked on has expired.")) # Reset code is valid. Now ask user to choose a new password form = PasswordResetForm() if form.validate_on_submit(): user.password = form.password.data db.session.delete(resetreq) db.session.commit() return render_message( title="Password reset complete", message=Markup( 'Your password has been reset. You may now <a href="%s">login</a> with your new password.' % escape(url_for("login")) ), ) return render_form( form=form, title="Reset password", formid="reset", submit="Reset password", message=Markup("Hello, <strong>%s</strong>. You may now choose a new password." % user.fullname), ajax=True, )
def reset_email(user, kwargs): resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs['secret']).first() if not resetreq: return render_message(title=_("Invalid reset link"), message=_(u"The reset link you clicked on is invalid")) if resetreq.created_at < utcnow() - timedelta(days=1): # Reset code has expired (> 24 hours). Delete it db.session.delete(resetreq) db.session.commit() return render_message(title=_("Expired reset link"), message=_(u"The reset link you clicked on has expired")) # Logout *after* validating the reset request to prevent DoS attacks on the user logout_internal() db.session.commit() # Reset code is valid. Now ask user to choose a new password form = PasswordResetForm() form.edit_user = user if form.validate_on_submit(): user.password = form.password.data db.session.delete(resetreq) db.session.commit() return render_message(title=_("Password reset complete"), message=Markup( _(u"Your password has been reset. You may now <a href=\"{loginurl}\">login</a> with your new password.").format( loginurl=escape(url_for('.login'))))) return render_form(form=form, title=_("Reset password"), formid='reset', submit=_("Reset password"), message=Markup(_(u"Hello, <strong>{fullname}</strong>. You may now choose a new password.").format( fullname=escape(user.fullname))), ajax=False)
def reset_email(user, kwargs): resetreq = PasswordResetRequest.query.filter_by(user=user, reset_code=kwargs['secret']).first() if not resetreq: return render_message(title=_("Invalid reset link"), message=_(u"The reset link you clicked on is invalid")) if resetreq.created_at < datetime.utcnow() - timedelta(days=1): # Reset code has expired (> 24 hours). Delete it db.session.delete(resetreq) db.session.commit() return render_message(title=_("Expired reset link"), message=_(u"The reset link you clicked on has expired")) # Logout *after* validating the reset request to prevent DoS attacks on the user logout_internal() db.session.commit() # Reset code is valid. Now ask user to choose a new password form = PasswordResetForm() form.edit_user = user if form.validate_on_submit(): user.password = form.password.data db.session.delete(resetreq) db.session.commit() return render_message(title=_("Password reset complete"), message=Markup( _(u"Your password has been reset. You may now <a href=\"{loginurl}\">login</a> with your new password.").format( loginurl=escape(url_for('.login'))))) return render_form(form=form, title=_("Reset password"), formid='reset', submit=_("Reset password"), message=Markup(_(u"Hello, <strong>{fullname}</strong>. You may now choose a new password.").format( fullname=escape(user.fullname))), ajax=False)
def confirm_email(md5sum, secret): emailclaim = UserEmailClaim.query.filter_by( md5sum=md5sum, verification_code=secret).first() if emailclaim is not None: if 'verify' in emailclaim.permissions(g.user): useremail = emailclaim.user.add_email( emailclaim.email, primary=emailclaim.user.email is None) db.session.delete(emailclaim) for claim in UserEmailClaim.query.filter_by( email=useremail.email).all(): db.session.delete(claim) db.session.commit() return render_message( title="Email address verified", message=Markup( "Hello %s! Your email address <code>%s</code> has now been verified." % (escape(emailclaim.user.fullname), escape( useremail.email)))) else: return render_message( title="That was not for you", message= u"You’ve opened an email verification link that was meant for another user. " u"If you are managing multiple accounts, please login with the correct account " u"and open the link again.", code=403) else: return render_message( title="Expired confirmation link", message= "The confirmation link you clicked on is either invalid or has expired.", code=404)
def profile_new(): # Step 1: Get a list of organizations this user owns existing = Profile.query.filter( Profile.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a profile new_profiles = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_profiles.append((org['userid'], org['title'])) if not new_profiles: return render_message( title=_(u"No organizations found"), message=Markup( _(u"You do not have any organizations that do not already have a Talkfunnel. " u'Would you like to <a href="{link}">create a new organization</a>?' ).format(link=lastuser.endpoint_url('/organizations/new')))) eligible_profiles = [] for orgid, title in new_profiles: if Team.query.filter_by(orgid=orgid).first() is not None: eligible_profiles.append((orgid, title)) if not eligible_profiles: return render_message( title=_(u"No organizations available"), message= _(u"To create a Talkfunnel for an organization, you must be the owner of the organization." )) # Step 3: Ask user to select organization form = NewProfileForm() form.profile.choices = eligible_profiles if request.method == 'GET': form.profile.data = new_profiles[0][0] if form.validate_on_submit(): # Step 4: Make a profile org = [ org for org in g.user.organizations_owned() if org['userid'] == form.profile.data ][0] profile = Profile(name=org['name'], title=org['title'], userid=org['userid']) db.session.add(profile) db.session.commit() flash( _(u"Created a profile for {profile}").format( profile=profile.title), "success") return render_redirect(profile.url_for('edit'), code=303) return render_form( form=form, title=_(u"Create a Talkfunnel for your organization..."), message= _(u"Talkfunnel is a free service while in beta. Sign up now to help us test the service." ), submit="Next", formid="profile_new", cancel_url=url_for('index'), ajax=False)
def workspace_new(): # Step 1: Get a list of organizations this user owns existing = Workspace.query.filter( Workspace.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a workspace new_workspaces = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_workspaces.append((org['userid'], org['title'])) if not new_workspaces: return render_message( title=u"No organizations found", message=Markup( u"You do not have any organizations that do not already have a workspace. " u'Would you like to <a href="%s">create a new organization</a>?' % lastuser.endpoint_url('/organizations/new'))) eligible_workspaces = [] for orgid, title in new_workspaces: if Team.query.filter_by(orgid=orgid).first() is not None: eligible_workspaces.append((orgid, title)) if not eligible_workspaces: return render_message( title=u"No organizations available", message=Markup( u"To create a workspace for an organization, you must first allow this app to " u"access the list of teams in your organization. " u'<a href="%s">Do that here</a>.' % lastuser.endpoint_url('/apps/' + lastuser.client_id))) # Step 3: Ask user to select organization form = NewWorkspaceForm() form.workspace.choices = eligible_workspaces if request.method == 'GET': form.workspace.data = new_workspaces[0][0] if form.validate_on_submit(): # Step 4: Make a workspace org = [ org for org in g.user.organizations_owned() if org['userid'] == form.workspace.data ][0] workspace = Workspace(name=org['name'], title=org['title'], userid=org['userid'], currency=form.currency.data, timezone=app.config.get('TIMEZONE', '')) db.session.add(workspace) db.session.commit() flash(u"Created a workspace for %s" % workspace.title, "success") return render_redirect(url_for('workspace_edit', workspace=workspace.name), code=303) return render_form(form=form, title="Create a workspace for your organization...", submit="Next", formid="workspace_new", cancel_url=url_for('index'), ajax=False)
def profile_new(): # Step 1: Get a list of organizations this user owns existing = Profile.query.filter( Profile.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a profile new_profiles = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_profiles.append((org['userid'], org['title'])) if not new_profiles: return render_message( title=_(u"No organizations found"), message=Markup( _(u"You do not have any organizations that do not already have a Talkfunnel. " u'Would you like to <a href="{link}">create a new organization</a>?' ).format(link=lastuser.endpoint_url('/organizations/new')))) eligible_profiles = [] for orgid, title in new_profiles: if Team.query.filter_by(orgid=orgid).first() is not None: eligible_profiles.append((orgid, title)) if not eligible_profiles: return render_message( title=_(u"No organizations available"), message= _(u"To create a Talkfunnel for an organization, you must be the owner of the organization." )) # Step 3: Ask user to select organization form = NewProfileForm() form.profile.choices = eligible_profiles if request.method == 'GET': form.profile.data = new_profiles[0][0] if form.validate_on_submit(): # Step 4: Make a profile org = [ org for org in g.user.organizations_owned() if org['userid'] == form.profile.data ][0] profile = Profile( name=org['name'], title=org['title'], userid=org['userid']) db.session.add(profile) db.session.commit() flash( _(u"Created a profile for {profile}").format( profile=profile.title), "success") return render_redirect(profile.url_for('edit'), code=303) return render_form( form=form, title=_(u"Create a Talkfunnel for your organization..."), message= _(u"Talkfunnel is a free service while in beta. Sign up now to help us test the service." ), submit="Next", formid="profile_new", cancel_url=url_for('index'), ajax=False)
def confirm_email(md5sum, secret): emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first() if emailclaim is not None: if "verify" in emailclaim.permissions(g.user): existing = UserEmail.query.filter(UserEmail.email.in_([emailclaim.email, emailclaim.email.lower()])).first() if existing is not None: claimed_email = emailclaim.email claimed_user = emailclaim.user db.session.delete(emailclaim) db.session.commit() if claimed_user != g.user: return render_message( title="Email address already claimed", message=Markup( "The email address <code>%s</code> has already been verified by another user." % escape(claimed_email) ), ) else: return render_message( title="Email address already verified", message=Markup( "Hello %s! Your email address <code>%s</code> has already been verified." % (escape(claimed_user.fullname), escape(claimed_email)) ), ) useremail = emailclaim.user.add_email(emailclaim.email.lower(), primary=emailclaim.user.email is None) db.session.delete(emailclaim) for claim in UserEmailClaim.query.filter( UserEmailClaim.email.in_([useremail.email, useremail.email.lower()]) ).all(): db.session.delete(claim) db.session.commit() user_data_changed.send(g.user, changes=["email"]) return render_message( title="Email address verified", message=Markup( "Hello %s! Your email address <code>%s</code> has now been verified." % (escape(emailclaim.user.fullname), escape(useremail.email)) ), ) else: return render_message( title="That was not for you", message=u"You’ve opened an email verification link that was meant for another user. " u"If you are managing multiple accounts, please login with the correct account " u"and open the link again.", code=403, ) else: return render_message( title="Expired confirmation link", message="The confirmation link you clicked on is either invalid or has expired.", code=404, )
def reset(): # User wants to reset password # Ask for username or email, verify it, and send a reset code form = PasswordResetRequestForm() if getbool(request.args.get('expired')): message = _(u"Your password has expired. Please enter your username " "or email address to request a reset code and set a new password") else: message = None if request.method == 'GET': form.username.data = request.args.get('username') if form.validate_on_submit(): username = form.username.data user = form.user if '@' in username and not username.startswith('@'): # They provided an email address. Send reset email to that address email = username else: # Send to their existing address # User.email is a UserEmail object email = unicode(user.email) if not email and user.emailclaims: email = user.emailclaims[0].email if not email: # They don't have an email address. Maybe they logged in via Twitter # and set a local username and password, but no email. Could happen. if len(user.externalids) > 0: extid = user.externalids[0] return render_message(title=_("Cannot reset password"), message=Markup(_(u""" We do not have an email address for your account. However, your account is linked to <strong>{service}</strong> with the id <strong>{username}</strong>. You can use that to login. """).format(service=login_registry[extid.service].title, username=extid.username or extid.userid))) else: return render_message(title=_("Cannot reset password"), message=Markup(_( u""" We do not have an email address for your account and therefore cannot email you a reset link. Please contact <a href="mailto:{email}">{email}</a> for assistance. """).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL'])))) resetreq = PasswordResetRequest(user=user) db.session.add(resetreq) send_password_reset_link(email=email, user=user, secret=resetreq.reset_code) db.session.commit() return render_message(title=_("Reset password"), message=_(u""" We sent a link to reset your password to your email address: {masked_email}. Please check your email. If it doesn’t arrive in a few minutes, it may have landed in your spam or junk folder. The reset link is valid for 24 hours. """.format(masked_email=mask_email(email)))) return render_form(form=form, title=_("Reset password"), message=message, submit=_("Send reset code"), ajax=False)
def reset(): # User wants to reset password # Ask for username or email, verify it, and send a reset code form = PasswordResetRequestForm() if getbool(request.args.get('expired')): message = _(u"Your password has expired. Please enter your username " "or email address to request a reset code and set a new password") else: message = None if request.method == 'GET': form.username.data = request.args.get('username') if form.validate_on_submit(): username = form.username.data user = form.user if '@' in username and not username.startswith('@'): # They provided an email address. Send reset email to that address email = username else: # Send to their existing address # User.email is a UserEmail object email = unicode(user.email) if not email and user.emailclaims: email = user.emailclaims[0].email if not email: # They don't have an email address. Maybe they logged in via Twitter # and set a local username and password, but no email. Could happen. if len(user.externalids) > 0: extid = user.externalids[0] return render_message(title=_("Cannot reset password"), message=Markup(_(u""" We do not have an email address for your account. However, your account is linked to <strong>{service}</strong> with the id <strong>{username}</strong>. You can use that to login. """).format(service=login_registry[extid.service].title, username=extid.username or extid.userid))) else: return render_message(title=_("Cannot reset password"), message=Markup(_( u""" We do not have an email address for your account and therefore cannot email you a reset link. Please contact <a href="mailto:{email}">{email}</a> for assistance. """).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL'])))) resetreq = PasswordResetRequest(user=user) db.session.add(resetreq) send_password_reset_link(email=email, user=user, secret=resetreq.reset_code) db.session.commit() return render_message(title=_("Reset password"), message=_(u""" We sent you an email with a link to reset your password. Please check your email. If it doesn’t arrive in a few minutes, it may have landed in your spam or junk folder. The reset link is valid for 24 hours. """)) return render_form(form=form, title=_("Reset password"), message=message, submit=_("Send reset code"), ajax=False)
def board_new(): # Step 1: Get a list of organizations this user owns existing = Board.query.filter(Board.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a board new_boards = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_boards.append((org['userid'], org['title'])) if not new_boards: return render_message( title=u"No organizations found", message=Markup(u"You do not have any organizations that do not already have a board. " u'Would you like to <a href="%s">create a new organization</a>?' % lastuser.endpoint_url('/organizations/new'))) # Step 3: Ask user to select organization form = NewBoardForm() form.board.choices = new_boards if request.method == 'GET': form.board.data = new_boards[0][0] if form.validate_on_submit(): # Step 4: Make a board org = [org for org in g.user.organizations_owned() if org['userid'] == form.board.data][0] board = Board(name=org['name'], title=org['title'], userid=org['userid']) db.session.add(board) db.session.commit() flash(u"Created a board for %s" % board.title, 'success') return render_redirect(url_for('board_edit', board=board.name), code=303) return render_form(form=form, title="Create a board for your organization...", submit="Next", formid="board_new", cancel_url=url_for('index'), ajax=False)
def video_remove(channel, playlist, video): """ Remove video from playlist """ if playlist not in video.playlists: # This video isn't in this playlist abort(404) # If this is the primary playlist for this video, refuse to remove it. if playlist == video.playlist: return render_message( title="Cannot remove", message=Markup( "Videos cannot be removed from their primary playlist. " '<a href="%s">Return to video</a>.' % video.url_for())) connection = PlaylistVideo.query.filter_by( playlist_id=playlist.id, video_id=video.id).first_or_404() return render_delete_sqla( connection, db, title="Confirm remove", message=u"Remove video '%s' from playlist '%s'?" % (video.title, playlist.title), success=u"You have removed video '%s' from playlist '%s'." % (video.title, playlist.title), next=playlist.url_for())
def video_remove(channel, playlist, video, kwargs): """ Remove video from playlist """ if playlist not in video.playlists: # This video isn't in this playlist abort(404) if channel.userid not in g.user.user_organization_ids(): # User doesn't own this playlist abort(403) if kwargs['video'] != video.url_name: # Video's URL has changed. Redirect user to prevent old/invalid names # showing in the URL return redirect(url_for('video_remove', channel=channel.name, playlist=playlist.name, video=video.url_name)) # If this is the primary playlist for this video, refuse to remove it. if playlist == video.playlist: return render_message(title="Cannot remove", message=Markup("Videos cannot be removed from their primary playlist. " '<a href="%s">Return to video</a>.' % url_for('video_view', channel=channel.name, playlist=playlist.name, video=video.url_name))) connection = PlaylistVideo.query.filter_by(playlist_id=playlist.id, video_id=video.id).first_or_404() return render_delete_sqla(connection, db, title="Confirm remove", message=u"Remove video '%s' from playlist '%s'?" % (video.title, playlist.title), success=u"You have removed video '%s' from playlist '%s'." % (video.title, playlist.title), next=url_for('playlist_view', channel=channel.name, playlist=playlist.name))
def workspace_new(): # Step 1: Get a list of organizations this user owns existing = Workspace.query.filter(Workspace.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a workspace new_workspaces = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_workspaces.append((org['userid'], org['title'])) if not new_workspaces: return render_message( title=u"No organizations remaining", message=u"You do not have any organizations that do not yet have a workspace.") # Step 3: Ask user to select organization form = NewWorkspaceForm() form.workspace.choices = new_workspaces if form.validate_on_submit(): # Step 4: Make a workspace org = [org for org in g.user.organizations_owned() if org['userid'] == form.workspace.data][0] workspace = Workspace(name=org['name'], title=org['title'], userid=org['userid'], currency=form.currency.data, fullname=form.fullname.data, address=form.address.data, cin=form.cin.data,pan=form.pan.data,tin=form.tin.data,tan=form.tan.data) db.session.add(workspace) db.session.commit() flash("Created new workspace for %s" % workspace.title, "success") return render_redirect(url_for('workspace_view', workspace=workspace.name), code=303) return render_form(form=form, title="Create a new organization workspace", submit="Create", formid="workspace_new", cancel_url=url_for('index'), ajax=False)
def confirm_email(md5sum, secret): emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first() if emailclaim is not None: if 'verify' in emailclaim.permissions(current_auth.user): existing = UserEmail.query.filter(UserEmail.email.in_([emailclaim.email, emailclaim.email.lower()])).first() if existing is not None: claimed_email = emailclaim.email claimed_user = emailclaim.user db.session.delete(emailclaim) db.session.commit() if claimed_user != current_auth.user: return render_message(title=_("Email address already claimed"), message=Markup( _(u"The email address <code>{email}</code> has already been verified by another user").format( email=escape(claimed_email)))) else: return render_message(title=_("Email address already verified"), message=Markup(_(u"Hello <strong>{fullname}</strong>! " u"Your email address <code>{email}</code> has already been verified").format( fullname=escape(claimed_user.fullname), email=escape(claimed_email)))) useremail = emailclaim.user.add_email(emailclaim.email, primary=emailclaim.user.email is None, type=emailclaim.type, private=emailclaim.private) db.session.delete(emailclaim) for claim in UserEmailClaim.query.filter( UserEmailClaim.email.in_([useremail.email, useremail.email.lower()])).all(): db.session.delete(claim) db.session.commit() user_data_changed.send(current_auth.user, changes=['email']) return render_message(title=_("Email address verified"), message=Markup(_(u"Hello <strong>{fullname}</strong>! " u"Your email address <code>{email}</code> has now been verified").format( fullname=escape(emailclaim.user.fullname), email=escape(useremail.email)))) else: return render_message( title=_("This was not for you"), message=_(u"You’ve opened an email verification link that was meant for another user. " u"If you are managing multiple accounts, please login with the correct account " u"and open the link again"), code=403) else: return render_message( title=_("Expired confirmation link"), message=_(u"The confirmation link you clicked on is either invalid or has expired"), code=404)
def reset(): # User wants to reset password # Ask for username or email, verify it, and send a reset code form = PasswordResetRequestForm() if form.validate_on_submit(): username = form.username.data user = form.user if "@" in username and not username.startswith("@"): # They provided an email address. Send reset email to that address email = username else: # Send to their existing address # User.email is a UserEmail object email = unicode(user.email) if not email: # They don't have an email address. Maybe they logged in via Twitter # and set a local username and password, but no email. Could happen. return render_message( title="Reset password", message=Markup( """ We do not have an email address for your account and therefore cannot email you a reset link. Please contact <a href="mailto:%s">%s</a> for assistance. """ % (escape(app.config["SITE_SUPPORT_EMAIL"]), escape(app.config["SITE_SUPPORT_EMAIL"])) ), ) resetreq = PasswordResetRequest(user=user) db.session.add(resetreq) send_password_reset_link(email=email, user=user, secret=resetreq.reset_code) db.session.commit() return render_message( title="Reset password", message=Markup( u""" You were sent an email at <code>%s</code> with a link to reset your password. Please check your email. If it doesn’t arrive in a few minutes, it may have landed in your spam or junk folder. The reset link is valid for 24 hours. """ % escape(email) ), ) return render_form(form=form, title="Reset password", submit="Send reset code", ajax=True)
def lastuser_error(error, error_description=None, error_uri=None): if error == 'access_denied': flash(_("You denied the request to login"), category='error') return redirect(get_next_url()) return render_message(title=_("Error: {error}").format(error=error), message=Markup( "<p>{desc}</p><p>URI: {uri}</p>".format( desc=escape(error_description or ''), uri=escape(error_uri or _('NA')))))
def workspace_delete(workspace): # Only allow workspaces to be deleted if they have no expense reports if workspace.reports: return render_message( title=u"Cannot delete this workspace", message=u"This workspace cannot be deleted because it contains expense reports.") return render_delete_sqla(workspace, db, title=u"Confirm delete", message=u"Delete workspace '%s'?" % workspace.title, success=u"You have deleted workspace '%s'." % workspace.title, next=url_for('index'))
def workspace_new(): # Step 1: Get a list of organizations this user owns existing = Workspace.query.filter(Workspace.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a workspace new_workspaces = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_workspaces.append((org['userid'], org['title'])) if not new_workspaces: return render_message( title=u"No organizations found", message=Markup(u"You do not have any organizations that do not already have a workspace. " u'Would you like to <a href="%s">create a new organization</a>?' % lastuser.endpoint_url('/organizations/new'))) eligible_workspaces = [] for orgid, title in new_workspaces: if Team.query.filter_by(orgid=orgid).first() is not None: eligible_workspaces.append((orgid, title)) if not eligible_workspaces: return render_message( title=u"No organizations available", message=Markup(u"To create a workspace for an organization, you must first allow this app to " u"access the list of teams in your organization. " u'<a href="%s">Do that here</a>.' % lastuser.endpoint_url('/apps/' + lastuser.client_id))) # Step 3: Ask user to select organization form = NewWorkspaceForm() form.workspace.choices = eligible_workspaces if request.method == 'GET': form.workspace.data = new_workspaces[0][0] if form.validate_on_submit(): # Step 4: Make a workspace org = [org for org in g.user.organizations_owned() if org['userid'] == form.workspace.data][0] workspace = Workspace(name=org['name'], title=org['title'], userid=org['userid'], currency=form.currency.data, timezone=app.config.get('TIMEZONE', '')) db.session.add(workspace) db.session.commit() flash(u"Created a workspace for %s" % workspace.title, "success") return render_redirect(url_for('workspace_edit', workspace=workspace.name), code=303) return render_form(form=form, title="Create a workspace for your organization...", submit="Next", formid="workspace_new", cancel_url=url_for('index'), ajax=False)
def reset(): # User wants to reset password # Ask for username or email, verify it, and send a reset code form = PasswordResetRequestForm() if form.validate_on_submit(): username = form.username.data user = form.user if '@' in username and not username.startswith('@'): # They provided an email address. Send reset email to that address email = username else: # Send to their existing address # User.email is a UserEmail object email = unicode(user.email) if not email: # They don't have an email address. Maybe they logged in via Twitter # and set a local username and password, but no email. Could happen. return render_message(title="Reset password", message=Markup(""" We do not have an email address for your account and therefore cannot email you a reset link. Please contact <a href="mailto:%s">%s</a> for assistance. """ % (escape(app.config['SITE_SUPPORT_EMAIL']), escape(app.config['SITE_SUPPORT_EMAIL'])))) resetreq = PasswordResetRequest(user=user) db.session.add(resetreq) send_password_reset_link(email=email, user=user, secret=resetreq.reset_code) db.session.commit() return render_message(title="Reset password", message=Markup(u""" You were sent an email at <code>%s</code> with a link to reset your password. Please check your email. If it doesn’t arrive in a few minutes, it may have landed in your spam or junk folder. The reset link is valid for 24 hours. """ % escape(email))) return render_form(form=form, title="Reset password", submit="Send reset code", ajax=True)
def lastuser_error(error, error_description=None, error_uri=None): if error == 'access_denied': flash("You denied the request to login", category='error') return redirect(get_next_url()) return render_message( title="Error: {0}".format(error), message=Markup( "<p>{desc}</p><p>URI: {uri}</p>".format( desc=escape(error_description or ''), uri=escape(error_uri or _('NA'))) ) )
def lastuser_error(error, error_description=None, error_uri=None): if error == "access_denied": flash(_(u"You denied the request to login"), category="error") return redirect(get_next_url()) return render_message( title=_(u"Error: {error}").format(error=error), message=Markup( u"<p>{desc}</p><p>URI: {uri}</p>".format( desc=escape(error_description or u""), uri=escape(error_uri or _(u"NA")) ) ), )
def reset_email(user, kwargs): logout_internal() resetreq = PasswordResetRequest.query.filter_by( user=user, reset_code=kwargs['secret']).first() if not resetreq: return render_message( title="Invalid reset link", message=Markup("The reset link you clicked on is invalid.")) if resetreq.created_at < datetime.utcnow() - timedelta(days=1): # Reset code has expired (> 24 hours). Delete it db.session.delete(resetreq) db.session.commit() return render_message( title="Expired reset link", message=Markup("The reset link you clicked on has expired.")) # Reset code is valid. Now ask user to choose a new password form = PasswordResetForm() if form.validate_on_submit(): user.password = form.password.data db.session.delete(resetreq) db.session.commit() return render_message( title="Password reset complete", message=Markup( 'Your password has been reset. You may now <a href="%s">login</a> with your new password.' % escape(url_for('login')))) return render_form( form=form, title="Reset password", formid='reset', submit="Reset password", message=Markup( 'Hello, <strong>%s</strong>. You may now choose a new password.' % user.fullname), ajax=True)
def confirm_email(md5sum, secret): emailclaim = UserEmailClaim.query.filter_by(md5sum=md5sum, verification_code=secret).first() if emailclaim is not None: if 'verify' in emailclaim.permissions(g.user): useremail = emailclaim.user.add_email(emailclaim.email, primary=emailclaim.user.email is None) db.session.delete(emailclaim) for claim in UserEmailClaim.query.filter_by(email=useremail.email).all(): db.session.delete(claim) db.session.commit() return render_message(title="Email address verified", message=Markup("Hello %s! Your email address <code>%s</code> has now been verified." % ( escape(emailclaim.user.fullname), escape(useremail.email)))) else: return render_message( title="That was not for you", message=u"You’ve opened an email verification link that was meant for another user. " u"If you are managing multiple accounts, please login with the correct account " u"and open the link again.", code=403) else: return render_message( title="Expired confirmation link", message="The confirmation link you clicked on is either invalid or has expired.", code=404)
def workspace_delete(workspace): # Only allow workspaces to be deleted if they have no expense reports if workspace.reports: return render_message( title=u"Cannot delete this workspace", message= u"This workspace cannot be deleted because it contains expense reports." ) return render_delete_sqla( workspace, db, title=u"Confirm delete", message=u"Delete workspace '%s'?" % workspace.title, success=u"You have deleted workspace '%s'." % workspace.title, next=url_for('index'))
def space_form_test(profile): fields = [{ 'name': 'test', 'label': 'Test Field', 'validators': ['Required'], }, { 'name': 'phone', 'type': 'AnnotatedTextField', 'prefix': '+91', }] form = FormGenerator().generate(fields)() if form.validate_on_submit(): class Target(object): pass target = Target() form.populate_obj(target) return render_message("Form submit", "Form content: " + repr(target.__dict__)) return render_form(form=form, title=_("Test form"), submit=_("Test submit"), cancel_url=profile.url_for())
def board_new(): # Step 1: Get a list of organizations this user owns existing = Board.query.filter( Board.userid.in_(g.user.organizations_owned_ids())).all() existing_ids = [e.userid for e in existing] # Step 2: Prune list to organizations without a board new_boards = [] for org in g.user.organizations_owned(): if org['userid'] not in existing_ids: new_boards.append((org['userid'], org['title'])) if not new_boards: return render_message( title=u"No organizations found", message=Markup( u"You do not have any organizations that do not already have a board. " u'Would you like to <a href="%s">create a new organization</a>?' % lastuser.endpoint_url('/organizations/new'))) # Step 3: Ask user to select organization form = NewBoardForm() form.board.choices = new_boards if request.method == 'GET': form.board.data = new_boards[0][0] if form.validate_on_submit(): # Step 4: Make a board org = [ org for org in g.user.organizations_owned() if org['userid'] == form.board.data ][0] board = Board(name=org['name'], title=org['title'], userid=org['userid']) db.session.add(board) db.session.commit() flash(u"Created a board for %s" % board.title, 'success') return render_redirect(url_for('board_edit', board=board.name), code=303) return render_form(form=form, title="Create a board for your organization...", submit="Next", formid="board_new", cancel_url=url_for('index'), ajax=False)
def video_remove(channel, playlist, video): """ Remove video from playlist """ if playlist not in video.playlists: # This video isn't in this playlist abort(404) # If this is the primary playlist for this video, refuse to remove it. if playlist == video.playlist: return render_message(title="Cannot remove", message=Markup("Videos cannot be removed from their primary playlist. " '<a href="%s">Return to video</a>.' % video.url_for())) connection = PlaylistVideo.query.filter_by(playlist_id=playlist.id, video_id=video.id).first_or_404() return render_delete_sqla(connection, db, title="Confirm remove", message=u"Remove video '%s' from playlist '%s'?" % (video.title, playlist.title), success=u"You have removed video '%s' from playlist '%s'." % (video.title, playlist.title), next=playlist.url_for())
def funnelapp_login(cookietest=False): # 1. Create a login nonce (single use, unlike CSRF) session['login_nonce'] = str(uuid.uuid4()) if not cookietest: # Reconstruct current URL with ?cookietest=1 or &cookietest=1 appended if request.query_string: return redirect(request.url + '&cookietest=1') return redirect(request.url + '?cookietest=1') if 'login_nonce' not in session: # No support for cookies. Abort login return render_message( title=_("Cookies required"), message=_("Please enable cookies in your browser."), ) # 2. Nonce has been set. Create a request code request_code = talkfunnel_serializer().dumps( {'nonce': session['login_nonce']}) # 3. Redirect user return redirect(app_url_for(app, 'login_talkfunnel', code=request_code))
def search(): return render_message(title="No search", message=u"Search hasn’t been implemented yet.")
def confirm_email(email_hash, secret): kwargs = blake2b_b58(email_hash) emailclaim = UserEmailClaim.get_by(verification_code=secret, **kwargs) if emailclaim is not None: emailclaim.email_address.mark_active() if 'verify' in emailclaim.permissions(current_auth.user): existing = UserEmail.get(email=emailclaim.email) if existing is not None: claimed_email = emailclaim.email claimed_user = emailclaim.user db.session.delete(emailclaim) db.session.commit() if claimed_user != current_auth.user: return render_message( title=_("Email address already claimed"), message=Markup( _("The email address <code>{email}</code> has already" " been verified by another user").format( email=escape(claimed_email))), ) else: return render_message( title=_("Email address already verified"), message=Markup( _("Hello <strong>{fullname}</strong>! Your email address" " <code>{email}</code> has already been verified" ).format( fullname=escape(claimed_user.fullname), email=escape(claimed_email), )), ) useremail = emailclaim.user.add_email( emailclaim.email, primary=emailclaim.user.email is None, type=emailclaim.type, private=emailclaim.private, ) for emailclaim in UserEmailClaim.all(useremail.email): db.session.delete(emailclaim) db.session.commit() user_data_changed.send(current_auth.user, changes=['email']) return render_message( title=_("Email address verified"), message=Markup( _("Hello <strong>{fullname}</strong>! " "Your email address <code>{email}</code> has now been verified" ).format( fullname=escape(useremail.user.fullname), email=escape(useremail.email), )), ) else: return render_message( title=_("This was not for you"), message= _("You’ve opened an email verification link that was meant for" " another user. If you are managing multiple accounts, please login" " with the correct account and open the link again."), code=403, ) else: return render_message( title=_("Expired confirmation link"), message= _("The confirmation link you clicked on is either invalid or has expired." ), code=404, )
def reset(): # User wants to reset password # Ask for username or email, verify it, and send a reset code form = PasswordResetRequestForm() if getbool(request.args.get('expired')): message = _( "Your password has expired. Please enter your username or email address to" " request a reset code and set a new password." ) else: message = None if request.method == 'GET': form.username.data = abort_null(request.args.get('username')) if form.validate_on_submit(): username = form.username.data user = form.user if '@' in username and not username.startswith('@'): # They provided an email address. Send reset email to that address email = username else: # Send to their existing address # User.email is a UserEmail object email = str(user.email) if not email and user.emailclaims: email = user.emailclaims[0].email if not email: # They don't have an email address. Maybe they logged in via Twitter # and set a local username and password, but no email. Could happen. if len(user.externalids) > 0: extid = user.externalids[0] return render_message( title=_("Cannot reset password"), message=Markup( _( "Your account does not have an email address. However," " it is linked to <strong>{service}</strong> with the id" " <strong>{username}</strong>. You can use that to login." ).format( service=login_registry[extid.service].title, username=extid.username or extid.userid, ) ), ) return render_message( title=_("Cannot reset password"), message=Markup( _( 'Your account does not have an email address. Please' ' contact <a href="mailto:{email}">{email}</a> for' ' assistance.' ).format(email=escape(current_app.config['SITE_SUPPORT_EMAIL'])) ), ) # Allow only two reset attempts per hour to discourage abuse validate_rate_limit('email_reset', user.uuid_b58, 2, 3600) send_password_reset_link( email=email, user=user, token=token_serializer().dumps( {'buid': user.buid, 'pw_set_at': str_pw_set_at(user)} ), ) return render_message( title=_("Email sent"), message=_( "You have been sent an email with a link to reset your password, to" " your address {masked_email}. If it doesn’t arrive in a few minutes," " it may have landed in your spam or junk folder. The reset link is" " valid for 24 hours." ).format(masked_email=mask_email(email)), ) return render_form( form=form, title=_("Reset password"), message=message, submit=_("Send reset link"), ajax=False, template='account_formlayout.html.jinja2', )
def reset_email_do(): # Validate the token # 1. Do we have a token? User may have accidentally landed here if 'temp_token' not in session: if request.method == 'GET': # No token. GET request. Either user landed here by accident, or browser # reloaded this page from history. Send back to to the reset request page return redirect(url_for('reset'), code=303) # Reset token was expired from session, likely because they didn't submit # the form in time. We no longer know what user this is for. Inform the user return render_message( title=_("Please try again"), message=_("This page timed out. Please open the reset link again."), ) # 2. There's a token in the session. Is it valid? try: # Allow 24 hours (86k seconds) validity for the reset token token = token_serializer().loads(session['temp_token'], max_age=86400) except itsdangerous.exc.SignatureExpired: # Link has expired (timeout). session.pop('temp_token', None) session.pop('temp_token_at', None) flash( _( "This password reset link has expired." " If you still need to reset your password, you may request a new link" ), 'error', ) return redirect(url_for('reset'), code=303) except itsdangerous.exc.BadData: # Link is invalid session.pop('temp_token', None) session.pop('temp_token_at', None) flash( _( "This password reset link is invalid." " If you still need to reset your password, you may request a new link" ), 'error', ) return redirect(url_for('reset'), code=303) # 3. We have a token and it's not expired. Is there a user? user = User.get(buid=token['buid']) if not user: # If the user has disappeared, it's likely because of account deletion. session.pop('temp_token', None) session.pop('temp_token_at', None) return render_message( title=_("Unknown user"), message=_("There is no account matching this password reset request"), ) # 4. We have a user. Has the token been used already? Check pw_set_at if token['pw_set_at'] != str_pw_set_at(user): # Token has been used to set a password, as the timestamp has changed. Ask user # if they want to reset their password again session.pop('temp_token', None) session.pop('temp_token_at', None) flash( _( "This password reset link has been used." " If you need to reset your password again, you may request a new link" ), 'error', ) return redirect(url_for('reset'), code=303) # All good? Proceed with request # Logout *after* validating the reset request to prevent DoS attacks on the user logout_internal() db.session.commit() # Reset code is valid. Now ask user to choose a new password form = PasswordResetForm() form.edit_user = user if form.validate_on_submit(): current_app.logger.info("Password strength %f", form.password_strength) user.password = form.password.data session.pop('temp_token', None) session.pop('temp_token_at', None) # Invalidate all of the user's active sessions counter = None for counter, user_session in enumerate(user.active_user_sessions.all()): user_session.revoke() db.session.commit() dispatch_notification(AccountPasswordNotification(document=user)) return render_message( title=_("Password reset complete"), message=_( "Your password has been changed. You may now login with your new" " password." ) if counter is None else ngettext( "Your password has been changed. As a precaution, you have been logged" " out of one other device. You may now login with your new password.", "Your password has been changed. As a precaution, you have been logged" " out of %(num)d other devices. You may now login with your new" " password.", counter + 1, ), ) # Form with id 'form-password-change' will have password strength meter on UI return render_form( form=form, title=_("Reset password"), formid='password-change', submit=_("Reset password"), message=Markup( _( "Hello, <strong>{fullname}</strong>. You may now choose a new password." ).format(fullname=escape(user.fullname)) ), ajax=False, template='account_formlayout.html.jinja2', )
def unsubscribe(self, token, token_type, cookietest=False): # This route strips the token from the URL before rendering the page, to avoid # leaking the token to web analytics software. # Step 1: Sanity check: someone loaded this URL without a token at all. # Send them away if not token and ( (request.method == 'GET' and 'temp_token' not in session) or (request.method == 'POST' and 'token' not in request.form)): return redirect(url_for('notification_preferences')) # Step 2: We have a URL token, but no `cookietest=1` in the URL. Copy token into # session and reload the page with the flag set if token and not cookietest: session['temp_token'] = token session['temp_token_type'] = token_type # Use naive datetime as the session can't handle tz-aware datetimes session['temp_token_at'] = datetime.utcnow() # These values are removed from the session in 10 minutes by # :func:`funnel.views.login_session.clear_expired_temp_token` if the user # abandons this page. # Reconstruct current URL with ?cookietest=1 or &cookietest=1 appended # and reload the page if request.query_string: return redirect(request.url + '&cookietest=1') return redirect(request.url + '?cookietest=1') # Step 3a: We have a URL token and cookietest is now set, but token is missing # from session. That typically means the browser is refusing to set cookies # on 30x redirects that originated off-site (such as a webmail app). Do a # meta-refresh redirect instead. It is less secure because browser extensions # may be able to read the URL during the brief period the page is rendered, # but so far there has been no indication of cookies not being set. if token and 'temp_token' not in session: session['temp_token'] = token session['temp_token_type'] = token_type session['temp_token_at'] = datetime.utcnow() return metarefresh_redirect( url_for('notification_unsubscribe_do') + ('?' + request.query_string.decode()) if request. query_string else '') # Step 3b: We have a URL token and cookietest is now set, and token is also in # session. Great! No browser cookie problem, so redirect again to remove the # token from the URL. This will hide it from web analytics software such as # Google Analytics and Matomo. if token and 'temp_token' in session: # Browser is okay with cookies. Do a 302 redirect # Strip out `cookietest=1` from the redirected URL return redirect( (url_for('notification_unsubscribe_do') + ('?' + request.query_string.decode()) if request.query_string else '').replace('?cookietest=1&', '?') # If cookietest is somehow at start .replace('?cookietest=1', '') # Or if it's solo .replace('&cookietest=1', '') # And/or if it's in the middle or end ) # Step 4. We have a token and it's been stripped from the URL. Process it based # on the token type. if not token_type: token_type = session.get( 'temp_token_type') or request.form['token_type'] # --- Signed tokens (email) if token_type == 'signed': # nosec try: # Token will be in session in the GET request, and in request.form # in the POST request because we'll move it over during the GET request. payload = token_serializer().loads( session.get('temp_token') or request.form['token'], max_age=365 * 24 * 60 * 60, # Validity 1 year (365 days) ) except itsdangerous.exc.SignatureExpired: # Link has expired. It's been over a year! discard_temp_token() flash(unsubscribe_link_expired, 'error') return redirect(url_for('notification_preferences'), code=303) except itsdangerous.exc.BadData: discard_temp_token() flash(unsubscribe_link_invalid, 'error') return redirect(url_for('notification_preferences'), code=303) # --- Cached tokens (SMS) elif token_type == 'cached': # nosec # Enforce a rate limit per IP on cached tokens, to slow down enumeration. # Some ISPs use carrier-grade NAT and will have a single IP for a very # large number of users, so we have generous limits. 100 unsubscribes per # 10 minutes (600s) per IP address. validate_rate_limit('sms_unsubscribe', request.remote_addr, 100, 600) payload = retrieve_cached_token( session.get('temp_token') or request.form['token']) if not payload: # No payload, meaning invalid token discard_temp_token() flash(unsubscribe_link_invalid, 'error') return redirect(url_for('notification_preferences'), code=303) if payload['timestamp'] < datetime.utcnow() - timedelta(days=7): # Link older than a week. Expire it discard_temp_token() flash(unsubscribe_link_expired, 'error') return redirect(url_for('notification_preferences'), code=303) else: # This is not supposed to happen abort(400) # Step 5. Validate whether the token matches the current user, if any # Do not allow links to be used across accounts. if current_auth.user and current_auth.user.buid != payload['buid']: return render_message( title=_("Unauthorized unsubscribe link"), message= _("This unsubscribe link is for someone else’s account. Please logout" " or use an incognito/private browsing session to use this link." ), ) # Step 6. Load the user. The contents of `payload` are defined in # :meth:`NotificationView.unsubscribe_token` above user = User.get(buid=payload['buid']) if payload['transport'] == 'email' and 'hash' in payload: email_address = EmailAddress.get(email_hash=payload['hash']) email_address.mark_active() db.session.commit() # TODO: Add active status for phone numbers and check here # Step 7. Ask the user to confirm unsubscribe. Do not unsubscribe on a GET # request as it may be triggered by link previews (for transports other than # email, or when an email is copy-pasted into a messenger app). form = UnsubscribeForm( edit_user=user, transport=payload['transport'], notification_type=payload['notification_type'], ) # Move the token from session to form. The session is swept every 10 minutes, # so if the user opens an unsubscribe link, wanders away and comes back later, # it'll be gone from session. It's safe for longer in the form, and doesn't # bear the leakage risk of being in the URL where analytics software can log it. if 'temp_token' in session: form.token.data = session['temp_token'] form.token_type.data = session['temp_token_type'] discard_temp_token() if form.validate_on_submit(): form.save_to_user() db.session.commit() return render_message( title=_("Preferences saved"), message=_("Your notification preferences have been updated"), ) return render_form( form=form, title=_("Notification preferences"), formid='unsubscribe-preferences', submit=_("Save preferences"), ajax=False, template='account_formlayout.html.jinja2', )
def search(): return render_message(title="No search", message=u"Search hasn’t been implemented yet.")