Merge pull request #61 from simple-login/custom-alias-oauth-authorize
Custom alias oauth authorize
This commit is contained in:
commit
d1baca67ff
|
@ -1,4 +1,4 @@
|
|||
from flask import render_template, redirect, url_for, flash, request, session
|
||||
from flask import render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from app.config import (
|
||||
|
@ -9,7 +9,7 @@ from app.dashboard.base import dashboard_bp
|
|||
from app.email_utils import email_belongs_to_alias_domains, get_email_domain_part
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import GenEmail, CustomDomain
|
||||
from app.models import GenEmail, CustomDomain, DeletedAlias
|
||||
from app.utils import convert_to_id, random_word, word_exist
|
||||
|
||||
|
||||
|
@ -49,7 +49,9 @@ def custom_alias():
|
|||
):
|
||||
full_alias = alias_prefix + alias_suffix
|
||||
|
||||
if GenEmail.get_by(email=full_alias):
|
||||
if GenEmail.get_by(email=full_alias) or DeletedAlias.get_by(
|
||||
email=full_alias
|
||||
):
|
||||
LOG.d("full alias already used %s", full_alias)
|
||||
flash(
|
||||
f"Alias {full_alias} already exists, please choose another one",
|
||||
|
|
|
@ -84,24 +84,42 @@
|
|||
<div class="col-md-9">
|
||||
<select class="custom-select custom-select" name="suggested-email">
|
||||
<option selected value="{{ suggested_email }}">{{ suggested_email }}</option>
|
||||
<option value="{{ personal_email }}">{{ personal_email }} (Personal Email)</option>
|
||||
<option value="{{ current_user.email }}">{{ current_user.email }} (Personal Email)</option>
|
||||
{% for email in other_emails %}
|
||||
<option value="{{ email }}">{{ email }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
{% if current_user.can_create_new_alias() %}
|
||||
<div class="mt-2">OR</div>
|
||||
<div style="display: flex; align-items: center" class="mt-2">
|
||||
<input class="form-control"
|
||||
pattern="[0-9a-z-_]{1,}"
|
||||
title="Only lowercase letter, number, dash (-), underscore (_) can be used in alias prefix."
|
||||
style="flex-grow: 2" name="custom-email-prefix">
|
||||
<input type="hidden" name="email-suffix" value="{{ email_suffix }}">
|
||||
<div class="ml-2">
|
||||
.{{ email_suffix }}@{{ EMAIL_DOMAIN }}
|
||||
<div class="mt-2 mb-2">OR</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6 pr-1 mb-1" style="min-width: 5em">
|
||||
<input name="prefix" class="form-control"
|
||||
type="text"
|
||||
pattern="[0-9a-z-_]{1,}"
|
||||
title="Only lowercase letter, number, dash (-), underscore (_) can be used in alias prefix."
|
||||
placeholder="email alias"
|
||||
autofocus>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-6"
|
||||
style="padding-left: 5px">
|
||||
<select class="form-control" name="suffix">
|
||||
{% for suffix in suffixes %}
|
||||
<option value="{{ suffix[1] }}">
|
||||
{% if suffix[0] %}
|
||||
{{ suffix[1] }} (your domain)
|
||||
{% else %}
|
||||
{{ suffix[1] }}
|
||||
{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small class="text-muted">
|
||||
Alias can use letter, number, dash and cannot be empty
|
||||
</small>
|
||||
|
|
|
@ -1,47 +1,64 @@
|
|||
{% extends "single.html" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block single_content %}
|
||||
<div class="row">
|
||||
<b>{{ client.name }}</b> would like to have access to your following data:
|
||||
|
||||
<ul class="mt-3">
|
||||
{% for scope in client.get_scopes() %}
|
||||
<li>
|
||||
{% if scope == Scope.AVATAR_URL %}
|
||||
avatar
|
||||
{% else %}
|
||||
{{ scope.value }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<label>
|
||||
In order to accept the request, you need to sign in.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="btn-group w-100">
|
||||
|
||||
<a href="{{ url_for('auth.login', next=next) }}" class="btn btn-success">
|
||||
Login
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('auth.register', next=next) }}" class="btn btn-info">
|
||||
Sign Up
|
||||
{% block content %}
|
||||
<div class="bg-white p-6" style="margin: auto; max-width: 600px">
|
||||
<div class="text-center mb-6">
|
||||
<a href="https://simplelogin.io">
|
||||
<img src="/static/logo.png" style="background-color: transparent; height: 40px">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div>
|
||||
<b>{{ client.name }}</b> would like to have access to your following data:
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul class="mt-3">
|
||||
{% for scope in client.get_scopes() %}
|
||||
<li>
|
||||
{% if scope == Scope.AVATAR_URL %}
|
||||
avatar
|
||||
{% else %}
|
||||
{{ scope.value }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
In order to accept the request, you need to sign in.
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="btn-group w-100">
|
||||
|
||||
<a href="{{ url_for('auth.login', next=next) }}" class="btn btn-success">
|
||||
Login
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('auth.register', next=next) }}" class="btn btn-info">
|
||||
Sign Up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="">
|
||||
<p class="text-center col">Cancel and go back to <b>{{ client.name }}</b></p>
|
||||
<a class="btn btn-block btn-secondary back-or-close">
|
||||
<i class="fe fe-arrow-left mr-2"></i>Cancel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="small-text mt-4">
|
||||
<a href="https://simplelogin.io">SimpleLogin</a> is an open source social login provider that protects your privacy.
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<p class="text-center col">Cancel and go back to <b>{{ client.name }}</b></p>
|
||||
<a class="btn btn-block btn-secondary back-or-close">
|
||||
<i class="fe fe-arrow-left mr-2"></i>Cancel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ from urllib.parse import urlparse
|
|||
from flask import request, render_template, redirect, flash
|
||||
from flask_login import current_user
|
||||
|
||||
from app.config import EMAIL_DOMAIN
|
||||
from app.config import EMAIL_DOMAIN, ALIAS_DOMAINS, DISABLE_ALIAS_SUFFIX
|
||||
from app.email_utils import get_email_domain_part
|
||||
from app.extensions import db
|
||||
from app.jose_utils import make_id_token
|
||||
from app.log import LOG
|
||||
|
@ -16,6 +17,7 @@ from app.models import (
|
|||
RedirectUri,
|
||||
OauthToken,
|
||||
DeletedAlias,
|
||||
CustomDomain,
|
||||
)
|
||||
from app.oauth.base import oauth_bp
|
||||
from app.oauth_models import (
|
||||
|
@ -103,21 +105,33 @@ def authorize():
|
|||
client.name
|
||||
)
|
||||
suggested_name, other_names = current_user.suggested_names()
|
||||
email_suffix = random_word()
|
||||
|
||||
user_custom_domains = [
|
||||
cd.domain for cd in current_user.verified_custom_domains()
|
||||
]
|
||||
# List of (is_custom_domain, alias-suffix)
|
||||
suffixes = []
|
||||
|
||||
# put custom domain first
|
||||
for alias_domain in user_custom_domains:
|
||||
suffixes.append((True, "@" + alias_domain))
|
||||
|
||||
# then default domain
|
||||
for domain in ALIAS_DOMAINS:
|
||||
suffixes.append(
|
||||
(
|
||||
False,
|
||||
("" if DISABLE_ALIAS_SUFFIX else "." + random_word())
|
||||
+ "@"
|
||||
+ domain,
|
||||
)
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"oauth/authorize.html",
|
||||
client=client,
|
||||
user_info=user_info,
|
||||
client_user=client_user,
|
||||
Scope=Scope,
|
||||
suggested_email=suggested_email,
|
||||
personal_email=current_user.email,
|
||||
suggested_name=suggested_name,
|
||||
other_names=other_names,
|
||||
other_emails=other_emails,
|
||||
email_suffix=email_suffix,
|
||||
EMAIL_DOMAIN=EMAIL_DOMAIN,
|
||||
**locals(),
|
||||
)
|
||||
else:
|
||||
# after user logs in, redirect user back to this page
|
||||
|
@ -127,7 +141,7 @@ def authorize():
|
|||
next=request.url,
|
||||
Scope=Scope,
|
||||
)
|
||||
else: # user allows or denies
|
||||
else: # POST - user allows or denies
|
||||
if request.form.get("button") == "deny":
|
||||
LOG.debug("User %s denies Client %s", current_user, client)
|
||||
final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
|
||||
|
@ -140,32 +154,55 @@ def authorize():
|
|||
if client_user:
|
||||
LOG.d("user %s has already allowed client %s", current_user, client)
|
||||
else:
|
||||
email_suffix = request.form.get("email-suffix")
|
||||
custom_email_prefix = request.form.get("custom-email-prefix")
|
||||
chosen_email = request.form.get("suggested-email")
|
||||
|
||||
suggested_name = request.form.get("suggested-name")
|
||||
custom_name = request.form.get("custom-name")
|
||||
|
||||
use_default_avatar = request.form.get("avatar-choice") == "default"
|
||||
alias_prefix = request.form.get("prefix")
|
||||
alias_suffix = request.form.get("suffix")
|
||||
|
||||
gen_email = None
|
||||
if custom_email_prefix:
|
||||
# check if user can generate custom email
|
||||
|
||||
# user creates a new alias, not using suggested alias
|
||||
if alias_prefix:
|
||||
# should never happen as this is checked on the front-end
|
||||
if not current_user.can_create_new_alias():
|
||||
raise Exception(f"User {current_user} cannot create custom email")
|
||||
|
||||
email = f"{convert_to_id(custom_email_prefix)}.{email_suffix}@{EMAIL_DOMAIN}"
|
||||
LOG.d("create custom email alias %s for user %s", email, current_user)
|
||||
user_custom_domains = [
|
||||
cd.domain for cd in current_user.verified_custom_domains()
|
||||
]
|
||||
|
||||
if GenEmail.get_by(email=email) or DeletedAlias.get_by(email=email):
|
||||
LOG.error("email %s already used, very rare!", email)
|
||||
flash(f"alias {email} already used", "error")
|
||||
from app.dashboard.views.custom_alias import verify_prefix_suffix
|
||||
|
||||
if verify_prefix_suffix(
|
||||
current_user, alias_prefix, alias_suffix, user_custom_domains
|
||||
):
|
||||
full_alias = alias_prefix + alias_suffix
|
||||
|
||||
if GenEmail.get_by(email=full_alias) or DeletedAlias.get_by(
|
||||
email=full_alias
|
||||
):
|
||||
LOG.error("alias %s already used, very rare!", full_alias)
|
||||
flash(f"Alias {full_alias} already used", "error")
|
||||
return redirect(request.url)
|
||||
else:
|
||||
gen_email = GenEmail.create(
|
||||
user_id=current_user.id, email=full_alias
|
||||
)
|
||||
|
||||
# get the custom_domain_id if alias is created with a custom domain
|
||||
alias_domain = get_email_domain_part(full_alias)
|
||||
custom_domain = CustomDomain.get_by(domain=alias_domain)
|
||||
if custom_domain:
|
||||
gen_email.custom_domain_id = custom_domain.id
|
||||
|
||||
db.session.flush()
|
||||
flash(f"Alias {full_alias} has been created", "success")
|
||||
# only happen if the request has been "hacked"
|
||||
else:
|
||||
flash("something went wrong", "warning")
|
||||
return redirect(request.url)
|
||||
|
||||
gen_email = GenEmail.create(email=email, user_id=current_user.id)
|
||||
db.session.flush()
|
||||
else: # user picks an email from suggestion
|
||||
# User chooses one of the suggestions
|
||||
else:
|
||||
chosen_email = request.form.get("suggested-email")
|
||||
# todo: add some checks on chosen_email
|
||||
if chosen_email != current_user.email:
|
||||
gen_email = GenEmail.get_by(email=chosen_email)
|
||||
if not gen_email:
|
||||
|
@ -174,6 +211,11 @@ def authorize():
|
|||
)
|
||||
db.session.flush()
|
||||
|
||||
suggested_name = request.form.get("suggested-name")
|
||||
custom_name = request.form.get("custom-name")
|
||||
|
||||
use_default_avatar = request.form.get("avatar-choice") == "default"
|
||||
|
||||
client_user = ClientUser.create(
|
||||
client_id=client.id, user_id=current_user.id
|
||||
)
|
||||
|
@ -181,20 +223,8 @@ def authorize():
|
|||
client_user.gen_email_id = gen_email.id
|
||||
|
||||
if custom_name:
|
||||
LOG.d(
|
||||
"use custom name %s for user %s client %s",
|
||||
custom_name,
|
||||
current_user,
|
||||
client,
|
||||
)
|
||||
client_user.name = custom_name
|
||||
elif suggested_name != current_user.name:
|
||||
LOG.d(
|
||||
"use another name %s for user %s client %s",
|
||||
custom_name,
|
||||
current_user,
|
||||
client,
|
||||
)
|
||||
client_user.name = suggested_name
|
||||
|
||||
if use_default_avatar:
|
||||
|
|
Loading…
Reference in a new issue