def test_can_be_used_as_personal_email(flask_client): # default alias domain assert not can_be_used_as_personal_email("*****@*****.**") assert not can_be_used_as_personal_email("*****@*****.**") assert can_be_used_as_personal_email("*****@*****.**") # custom domain user = User.create( email="[email protected]", password="******", name="Test User", activated=True ) db.session.commit() CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True) db.session.commit() assert not can_be_used_as_personal_email("*****@*****.**")
def register(): if current_user.is_authenticated: LOG.d("user is already authenticated, redirect to dashboard") flash("You are already logged in", "warning") return redirect(url_for("dashboard.index")) form = RegisterForm(request.form) next_url = request.args.get("next") if form.validate_on_submit(): email = form.email.data if not can_be_used_as_personal_email(email): flash( "You cannot use this email address as your personal inbox.", "error", ) else: user = User.filter_by(email=email).first() if user: flash(f"Email {form.email.data} already exists", "warning") else: LOG.debug("create user %s", form.email.data) user = User.create( email=form.email.data.lower(), name="", password=form.password.data, ) db.session.commit() send_activation_email(user, next_url) return render_template("auth/register_waiting_activation.html") return render_template("auth/register.html", form=form, next_url=next_url)
def auth_google(): """ Authenticate user with Facebook Input: google_token: Google access token device: to create an ApiKey associated with this device Output: 200 and user info containing: { name: "John Wick", mfa_enabled: true, mfa_key: "a long string", api_key: "a long string" } """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 google_token = data.get("google_token") device = data.get("device") cred = google.oauth2.credentials.Credentials(token=google_token) build = googleapiclient.discovery.build("oauth2", "v2", credentials=cred) user_info = build.userinfo().get().execute() email = user_info.get("email") user = User.get_by(email=email) if not user: if DISABLE_REGISTRATION: return jsonify(error="registration is closed"), 400 if not can_be_used_as_personal_email(email) or email_already_used( email): return jsonify(error=f"cannot use {email} as personal inbox"), 400 LOG.d("create Google user with %s", user_info) user = User.create(email=email.lower(), name="", activated=True) db.session.commit() email_utils.send_welcome_email(user) if not SocialAuth.get_by(user_id=user.id, social="google"): SocialAuth.create(user_id=user.id, social="google") db.session.commit() return jsonify(**auth_payload(user, device)), 200
def auth_facebook(): """ Authenticate user with Facebook Input: facebook_token: facebook access token device: to create an ApiKey associated with this device Output: 200 and user info containing: { name: "John Wick", mfa_enabled: true, mfa_key: "a long string", api_key: "a long string" } """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 facebook_token = data.get("facebook_token") device = data.get("device") graph = facebook.GraphAPI(access_token=facebook_token) user_info = graph.get_object("me", fields="email,name") email = user_info.get("email") user = User.get_by(email=email) if not user: if DISABLE_REGISTRATION: return jsonify(error="registration is closed"), 400 if not can_be_used_as_personal_email(email) or email_already_used( email): return jsonify(error=f"cannot use {email} as personal inbox"), 400 LOG.d("create facebook user with %s", user_info) user = User.create(email=email.lower(), name=user_info["name"], activated=True) db.session.commit() email_utils.send_welcome_email(user) if not SocialAuth.get_by(user_id=user.id, social="facebook"): SocialAuth.create(user_id=user.id, social="facebook") db.session.commit() return jsonify(**auth_payload(user, device)), 200
def auth_register(): """ User signs up - will need to activate their account with an activation code. Input: email password Output: 200: user needs to confirm their account """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 email = data.get("email") password = data.get("password") if DISABLE_REGISTRATION: return jsonify(error="registration is closed"), 400 if not can_be_used_as_personal_email(email) or email_already_used(email): return jsonify(error=f"cannot use {email} as personal inbox"), 400 if not password or len(password) < 8: return jsonify(error="password too short"), 400 LOG.debug("create user %s", email) user = User.create(email=email, name="", password=password) db.session.flush() # create activation code code = "".join([str(random.randint(0, 9)) for _ in range(6)]) AccountActivation.create(user_id=user.id, code=code) db.session.commit() send_email( email, f"Just one more step to join SimpleLogin", render("transactional/code-activation.txt", code=code), render("transactional/code-activation.html", code=code), ) return jsonify(msg="User needs to confirm their account"), 200
def github_callback(): # user clicks on cancel if "error" in request.args: flash("Please use another sign in method then", "warning") return redirect("/") github = OAuth2Session( GITHUB_CLIENT_ID, state=session["oauth_state"], scope=["user:email"], redirect_uri=_redirect_uri, ) token = github.fetch_token( _token_url, client_secret=GITHUB_CLIENT_SECRET, authorization_response=request.url, ) # a dict with "name", "login" github_user_data = github.get("https://api.github.com/user").json() LOG.d("user login with github %s", github_user_data) # return list of emails # { # 'email': '*****@*****.**', # 'primary': False, # 'verified': True, # 'visibility': None # } emails = github.get("https://api.github.com/user/emails").json() # only take the primary email email = None for e in emails: if e.get("verified") and e.get("primary"): email = e.get("email") break if not email: raise Exception("cannot get email for github user") user = User.get_by(email=email) # create user if not user: if not can_be_used_as_personal_email(email): flash( f"You cannot use {email} as your personal inbox.", "error", ) return redirect(url_for("auth.login")) LOG.d("create github user") user = User.create( email=email.lower(), name=github_user_data.get("name") or "", activated=True ) db.session.commit() login_user(user) email_utils.send_welcome_email(user.email, user.name) flash(f"Welcome to SimpleLogin {user.name}!", "success") # The activation link contains the original page, for ex authorize page next_url = request.args.get("next") if request.args else None return after_login(user, next_url)
def setting(): form = SettingForm() promo_form = PromoCodeForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash(f"Your profile has been updated", "success") if (form.email.data and form.email.data != current_user.email and not pending_email): new_email = form.email.data # check if this email is not used by other user, or as alias if (User.get_by(email=new_email) or GenEmail.get_by(email=new_email) or DeletedAlias.get_by(email=new_email)): flash(f"Email {new_email} already used", "error") elif not can_be_used_as_personal_email(new_email): flash( "You cannot use this email address as your personal inbox.", "error", ) else: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) elif request.form.get("form-name") == "change-password": send_reset_password_email(current_user) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") elif request.form.get("form-name") == "delete-account": User.delete(current_user.id) db.session.commit() flash("Your account has been deleted", "success") logout_user() return redirect(url_for("auth.register")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") elif request.form.get("form-name") == "export-data": data = { "email": current_user.email, "name": current_user.name, "aliases": [], "apps": [], "custom_domains": [], } for alias in GenEmail.filter_by( user_id=current_user.id).all(): # type: GenEmail data["aliases"].append( dict(email=alias.email, enabled=alias.enabled)) for custom_domain in CustomDomain.filter_by( user_id=current_user.id).all(): data["custom_domains"].append(custom_domain.domain) for app in Client.filter_by( user_id=current_user.id): # type: Client data["apps"].append( dict(name=app.name, home_url=app.home_url, published=app.published)) return Response( json.dumps(data), mimetype="text/json", headers={ "Content-Disposition": "attachment;filename=data.json" }, ) return redirect(url_for("dashboard.setting")) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, promo_form=promo_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, )
def mailbox_route(): mailboxes = Mailbox.query.filter_by(user_id=current_user.id).all() new_mailbox_form = NewMailboxForm() if request.method == "POST": if request.form.get("form-name") == "delete": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("You cannot delete default mailbox", "error") return redirect(url_for("dashboard.mailbox_route")) email = mailbox.email Mailbox.delete(mailbox_id) db.session.commit() flash(f"Mailbox {email} has been deleted", "success") return redirect(url_for("dashboard.mailbox_route")) if request.form.get("form-name") == "set-default": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("This mailbox is already default one", "error") return redirect(url_for("dashboard.mailbox_route")) if not mailbox.verified: flash("Cannot set unverified mailbox as default", "error") return redirect(url_for("dashboard.mailbox_route")) current_user.default_mailbox_id = mailbox.id db.session.commit() flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success") return redirect(url_for("dashboard.mailbox_route")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add additional mailbox", "warning") return redirect(url_for("dashboard.mailbox_route")) if new_mailbox_form.validate(): mailbox_email = new_mailbox_form.email.data.lower() if mailbox_already_used(mailbox_email, current_user): flash(f"{mailbox_email} already used", "error") elif not can_be_used_as_personal_email(mailbox_email): flash(f"You cannot use {mailbox_email}.", "error") else: new_mailbox = Mailbox.create(email=mailbox_email, user_id=current_user.id) db.session.commit() s = Signer(MAILBOX_SECRET) mailbox_id_signed = s.sign(str(new_mailbox.id)).decode() verification_url = (URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}") send_email( mailbox_email, f"Please confirm your email {mailbox_email}", render( "transactional/verify-mailbox.txt", user=current_user, link=verification_url, mailbox_email=mailbox_email, ), render( "transactional/verify-mailbox.html", user=current_user, link=verification_url, mailbox_email=mailbox_email, ), ) flash( f"You are going to receive an email to confirm {mailbox_email}.", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=new_mailbox.id)) return render_template( "dashboard/mailbox.html", mailboxes=mailboxes, new_mailbox_form=new_mailbox_form, EMAIL_DOMAIN=EMAIL_DOMAIN, ALIAS_DOMAINS=ALIAS_DOMAINS, )