remove totp, cleanup, bugfixes
This commit is contained in:
parent
bd7d8e4398
commit
368f2396ce
16
README.md
16
README.md
|
@ -2,7 +2,7 @@ Lenticular Cloud
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
||||||
Simple user Manager in LDAP
|
Simple user Manager proudly made in ~~LDAP~~ SQL
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,15 +11,12 @@ Features
|
||||||
|
|
||||||
* frontend for hydra
|
* frontend for hydra
|
||||||
* Web Platform to mange users
|
* Web Platform to mange users
|
||||||
* client certs
|
* fake ldap backend, can be used by other services
|
||||||
* ldap backend, can be used by other services
|
|
||||||
|
|
||||||
Auth Methods:
|
Auth Methods:
|
||||||
-------------
|
-------------
|
||||||
* U2F (TODO)
|
|
||||||
* TOTP
|
|
||||||
* Password
|
* Password
|
||||||
* WebAuth (TODO)
|
* Passkey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,13 +31,6 @@ Tested Services
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Oauth2 Settings:
|
|
||||||
----------------
|
|
||||||
|
|
||||||
callback url: `${domain}/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
|
|
@ -112,39 +112,6 @@ window.auth_passkey = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
window.totp = {
|
|
||||||
init_list: function(){
|
|
||||||
},
|
|
||||||
init_new: function() {
|
|
||||||
//create new TOTP secret, create qrcode and ask for token.
|
|
||||||
var form = $('form');
|
|
||||||
var secret = randBase32();
|
|
||||||
var input_secret = form.querySelector('#secret')
|
|
||||||
if(input_secret.value == '') {
|
|
||||||
input_secret.value = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.querySelector('#name').onchange=window.totp.generate_qrcode;
|
|
||||||
form.querySelector('#name').onkeyup=window.totp.generate_qrcode;
|
|
||||||
window.totp.generate_qrcode();
|
|
||||||
},
|
|
||||||
generate_qrcode: function(){
|
|
||||||
var form = $('form');
|
|
||||||
var secret = form.querySelector('#secret').value;
|
|
||||||
var name = form.querySelector('#name').value;
|
|
||||||
var issuer = 'Lenticular%20Cloud';
|
|
||||||
var svg_container = $('#svg-container')
|
|
||||||
var svg = new QRCode(`otpauth://totp/${issuer}:${name}?secret=${secret}&issuer=${issuer}`).svg();
|
|
||||||
var svg_xml =new DOMParser().parseFromString(svg,'text/xml')
|
|
||||||
if(svg_container.childNodes.length > 0) {
|
|
||||||
svg_container.childNodes[0].replaceWith(svg_xml.childNodes[0])
|
|
||||||
} else {
|
|
||||||
svg_container.appendChild(svg_xml.childNodes[0]);
|
|
||||||
}
|
|
||||||
// .innerHtml=svg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.password_change= {
|
window.password_change= {
|
||||||
init: function(){
|
init: function(){
|
||||||
var form = $('form');
|
var form = $('form');
|
||||||
|
@ -182,77 +149,3 @@ window.oauth2_token = {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.client_cert = {
|
|
||||||
init_list: function() {
|
|
||||||
// do fancy cert stats stuff
|
|
||||||
},
|
|
||||||
init_new: function() {
|
|
||||||
// create localy key or import public key
|
|
||||||
|
|
||||||
var form = $('form#gen-key-form');
|
|
||||||
|
|
||||||
},
|
|
||||||
generate_private_key: function() {
|
|
||||||
var form = $('form#gen-key-form');
|
|
||||||
var key_size = form.querySelector('#key-size').value;
|
|
||||||
var valid_time = form.querySelector('input[name=valid_time]').value;
|
|
||||||
$('button#generate-key').style['display'] = 'none';
|
|
||||||
pki.rsa.generateKeyPair({bits: key_size, workers: 2}, function(err, keypair) {
|
|
||||||
console.log(keypair);
|
|
||||||
|
|
||||||
//returns the exported key to a hidden form
|
|
||||||
var form_sign_key = $('#gen-key-sign form');
|
|
||||||
form_sign_key.querySelector('textarea[name=publickey]').value = pki.publicKeyToPem(keypair.publicKey);
|
|
||||||
form_sign_key.querySelector('input[name=valid_time]').value = valid_time;
|
|
||||||
|
|
||||||
SimpleFormSubmit.submitForm(form_sign_key.action, form_sign_key)
|
|
||||||
.then(response => {
|
|
||||||
response.json().then( json_data => {
|
|
||||||
if (json_data.errors) {
|
|
||||||
var msg ='<ul>';
|
|
||||||
for( var field in json_data.repsonse) {
|
|
||||||
msg += `<li>${field}: ${data.errors[field]}</li>`;
|
|
||||||
}
|
|
||||||
msg += '</ul>';
|
|
||||||
new Dialog('Password change Error', `Error Happend: ${msg}`).show()
|
|
||||||
} else {
|
|
||||||
// get certificate
|
|
||||||
var data = response.data;
|
|
||||||
var certs = [
|
|
||||||
pki.certificateFromPem(data.cert),
|
|
||||||
pki.certificateFromPem(data.ca_cert)
|
|
||||||
];
|
|
||||||
var password = form.querySelector('#cert-password').value;
|
|
||||||
var p12Asn1;
|
|
||||||
if (password == '') {
|
|
||||||
p12Asn1 = pkcs12.toPkcs12Asn1(keypair.privateKey, certs, null, {algorithm: '3des'}); // without password
|
|
||||||
} else {
|
|
||||||
p12Asn1 = pkcs12.toPkcs12Asn1(keypair.privateKey, certs, password, {algorithm: '3des'}); // without password
|
|
||||||
}
|
|
||||||
var p12Der = asn1.toDer(p12Asn1).getBytes();
|
|
||||||
var p12b64 = util.encode64(p12Der);
|
|
||||||
|
|
||||||
|
|
||||||
var button = $('#save-button');
|
|
||||||
button.href= "data:application/x-pkcs12;base64," + p12b64
|
|
||||||
button.style['display'] ='block';
|
|
||||||
//new Dialog('Password changed', 'Password changed successfully!').show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
revoke_certificate: function(href, id){
|
|
||||||
var dialog = new ConfirmDialog('Revoke client certificate', `Are you sure to revoke the certificate with the fingerprint ${id}?`);
|
|
||||||
dialog.show().then(()=>{
|
|
||||||
fetch(href, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -47,38 +47,8 @@ class PasswordAuthProvider(AuthProvider):
|
||||||
return compare_hash(crypt.crypt(password, user.password_hashed),user.password_hashed)
|
return compare_hash(crypt.crypt(password, user.password_hashed),user.password_hashed)
|
||||||
|
|
||||||
|
|
||||||
class U2FAuthProvider(AuthProvider):
|
|
||||||
@staticmethod
|
|
||||||
def get_from() -> FlaskForm:
|
|
||||||
return Fido2Form(prefix='fido2')
|
|
||||||
|
|
||||||
|
|
||||||
class WebAuthProvider(AuthProvider):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TotpAuthProvider(AuthProvider):
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_form():
|
|
||||||
return TotpForm(prefix='totp')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_auth(user: User, form: FlaskForm) -> bool:
|
|
||||||
data = form.data['totp']
|
|
||||||
if data is not None:
|
|
||||||
#print(f'data totp: {data}')
|
|
||||||
if len(user.totps) == 0: # migration, TODO remove
|
|
||||||
return True
|
|
||||||
for totp in user.totps:
|
|
||||||
if totp.verify(data):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_PROVIDER_LIST = [
|
AUTH_PROVIDER_LIST = [
|
||||||
PasswordAuthProvider,
|
PasswordAuthProvider
|
||||||
TotpAuthProvider
|
|
||||||
]
|
]
|
||||||
|
|
||||||
#print(LdapAuthProvider.get_name())
|
#print(LdapAuthProvider.get_name())
|
||||||
|
|
|
@ -20,13 +20,6 @@ class PasswordForm(FlaskForm):
|
||||||
password = PasswordField(gettext('Password'))
|
password = PasswordField(gettext('Password'))
|
||||||
submit = SubmitField(gettext('Authorize'))
|
submit = SubmitField(gettext('Authorize'))
|
||||||
|
|
||||||
|
|
||||||
class TotpForm(FlaskForm):
|
|
||||||
totp = StringField(gettext('2FA Token'))
|
|
||||||
submit = SubmitField(gettext('Authorize'))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConsentForm(FlaskForm):
|
class ConsentForm(FlaskForm):
|
||||||
# scopes = SelectMultipleField(gettext('scopes'))
|
# scopes = SelectMultipleField(gettext('scopes'))
|
||||||
# audiences = SelectMultipleField(gettext('audiences'))
|
# audiences = SelectMultipleField(gettext('audiences'))
|
||||||
|
|
|
@ -22,17 +22,6 @@ class ClientCertForm(FlaskForm):
|
||||||
])
|
])
|
||||||
submit = SubmitField(gettext('Submit'))
|
submit = SubmitField(gettext('Submit'))
|
||||||
|
|
||||||
|
|
||||||
class TOTPForm(FlaskForm):
|
|
||||||
secret = HiddenField(gettext('totp-Secret'))
|
|
||||||
token = StringField(gettext('totp-verify token'))
|
|
||||||
name = StringField(gettext('name'))
|
|
||||||
submit = SubmitField(gettext('Activate'))
|
|
||||||
|
|
||||||
|
|
||||||
class TOTPDeleteForm(FlaskForm):
|
|
||||||
submit = SubmitField(gettext('Delete'))
|
|
||||||
|
|
||||||
class AppTokenForm(FlaskForm):
|
class AppTokenForm(FlaskForm):
|
||||||
name = StringField(gettext('name'), validators=[DataRequired(),Length(min=1, max=255) ])
|
name = StringField(gettext('name'), validators=[DataRequired(),Length(min=1, max=255) ])
|
||||||
scopes = StringField(gettext('scopes'), validators=[DataRequired(),Length(min=1, max=255) ])
|
scopes = StringField(gettext('scopes'), validators=[DataRequired(),Length(min=1, max=255) ])
|
||||||
|
@ -41,11 +30,10 @@ class AppTokenForm(FlaskForm):
|
||||||
class AppTokenDeleteForm(FlaskForm):
|
class AppTokenDeleteForm(FlaskForm):
|
||||||
submit = SubmitField(gettext('Delete'))
|
submit = SubmitField(gettext('Delete'))
|
||||||
|
|
||||||
class WebauthnRegisterForm(FlaskForm):
|
class PasskeyRegisterForm(FlaskForm):
|
||||||
"""webauthn register token form"""
|
"""Passkey register form"""
|
||||||
|
|
||||||
attestation = HiddenField('Attestation', [InputRequired()])
|
name = StringField('Name', [Length(max=50)])
|
||||||
name = StringField('Name', [Length(max=250)])
|
|
||||||
submit = SubmitField('Register', render_kw={'disabled': True})
|
submit = SubmitField('Register', render_kw={'disabled': True})
|
||||||
|
|
||||||
class PasswordChangeForm(FlaskForm):
|
class PasswordChangeForm(FlaskForm):
|
||||||
|
|
|
@ -163,12 +163,8 @@ class User(BaseModel, ModelUpdatedMixin):
|
||||||
enabled: Mapped[bool] = mapped_column(db.Boolean, nullable=False, default=False)
|
enabled: Mapped[bool] = mapped_column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
app_tokens: Mapped[List['AppToken']] = relationship('AppToken', back_populates='user')
|
app_tokens: Mapped[List['AppToken']] = relationship('AppToken', back_populates='user')
|
||||||
# totps: Mapped[List['Totp']] = relationship('Totp', back_populates='user', default_factory=list)
|
|
||||||
passkey_credentials: Mapped[List['PasskeyCredential']] = relationship('PasskeyCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True)
|
passkey_credentials: Mapped[List['PasskeyCredential']] = relationship('PasskeyCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True)
|
||||||
|
|
||||||
@property
|
|
||||||
def totps(self) -> List['Totp']:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -226,20 +222,6 @@ class AppToken(BaseModel, ModelUpdatedMixin):
|
||||||
token = ''.join(secrets.choice(alphabet) for i in range(12))
|
token = ''.join(secrets.choice(alphabet) for i in range(12))
|
||||||
return AppToken(scopes=scopes, token=token, user=user, name=name)
|
return AppToken(scopes=scopes, token=token, user=user, name=name)
|
||||||
|
|
||||||
class Totp(BaseModel, ModelUpdatedMixin):
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
secret: Mapped[str] = mapped_column(db.String, nullable=False)
|
|
||||||
name: Mapped[str] = mapped_column(db.String, nullable=False)
|
|
||||||
|
|
||||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
||||||
db.Uuid,
|
|
||||||
db.ForeignKey(User.id), nullable=False)
|
|
||||||
# user: Mapped[User] = relationship(User, back_populates="totp")
|
|
||||||
last_used: Mapped[Optional[datetime]] = mapped_column(db.DateTime, nullable=True, default=None)
|
|
||||||
|
|
||||||
def verify(self, token: str) -> bool:
|
|
||||||
totp = pyotp.TOTP(self.secret)
|
|
||||||
return totp.verify(token)
|
|
||||||
|
|
||||||
|
|
||||||
class PasskeyCredential(BaseModel, ModelUpdatedMixin): # pylint: disable=too-few-public-methods
|
class PasskeyCredential(BaseModel, ModelUpdatedMixin): # pylint: disable=too-few-public-methods
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
{#<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.client_cert') }}">{{ gettext('Client Cert') }}</a></li>#}
|
{#<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.client_cert') }}">{{ gettext('Client Cert') }}</a></li>#}
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.passkey') }}">{{ gettext('Passkey') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.passkey') }}">{{ gettext('Passkey') }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.app_token') }}">{{ gettext('App Tokens') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.app_token') }}">{{ gettext('App Tokens') }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.totp') }}">{{ gettext('2FA - TOTP') }}</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.oauth2_tokens') }}">{{ gettext('Oauth2 Tokens') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.oauth2_tokens') }}">{{ gettext('Oauth2 Tokens') }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.password_change') }}">{{ gettext('Password Change') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.password_change') }}">{{ gettext('Password Change') }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a></li>
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
<td>{{ credential.credential_id[0:8].hex() }}...</td>
|
<td>{{ credential.credential_id[0:8].hex() }}...</td>
|
||||||
<td>{{ credential.last_used }}</td>
|
<td>{{ credential.last_used }}</td>
|
||||||
<td>{{ credential.created_at }}...</td>
|
<td>{{ credential.created_at }}...</td>
|
||||||
<td>{{ render_form(button_form, action_url=url_for('.passkey_delete', id=credential.id), action_text='delete', btn_class='btn btn-danger') }}</td>
|
<td>
|
||||||
|
{{ render_form(button_form, action_url=url_for('.passkey_delete', id=credential.id), action_text='delete', btn_class='btn btn-danger') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.passkey_new')}}">Add new Passkey</a>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
{% extends 'frontend/base.html.j2' %}
|
|
||||||
|
|
||||||
{% block title %}{{ gettext('2FA - TOTP') }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>name</th>
|
|
||||||
<th>created_at</th>
|
|
||||||
<th>action<th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for totp in current_user.totps %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ totp.name }}</td>
|
|
||||||
<td>{{ totp.created_at }}</td>
|
|
||||||
<td>{{ render_form(delete_form, action_url=url_for('frontend.totp_delete', totp_name=totp.name)) }}</td>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
<a class="btn btn-default" href="{{ url_for('frontend.totp_new') }}">
|
|
||||||
New TOTP
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block script_js %}
|
|
||||||
|
|
||||||
totp.init_list();
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% extends 'frontend/base.html.j2' %}
|
|
||||||
|
|
||||||
{% block title %}{{ gettext('2FA - TOTP - New') }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
{{ render_form(form) }}
|
|
||||||
|
|
||||||
<div id="svg-container">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block script_js %}
|
|
||||||
|
|
||||||
totp.init_new();
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -51,9 +51,13 @@ async def consent() -> ResponseReturnValue:
|
||||||
|
|
||||||
if form.validate_on_submit() or consent_request.skip:
|
if form.validate_on_submit() or consent_request.skip:
|
||||||
|
|
||||||
|
if type(consent_request.subject) != str:
|
||||||
|
logger.error("not set subject `consent_request.subject`")
|
||||||
|
return 'internal error', 500
|
||||||
uid = UUID(consent_request.subject)
|
uid = UUID(consent_request.subject)
|
||||||
user = User.query.get(uid)
|
user = User.query.get(uid)
|
||||||
if user is None:
|
if user is None:
|
||||||
|
logger.error("user not found, even if it should exist")
|
||||||
return 'internal error', 500
|
return 'internal error', 500
|
||||||
access_token = {
|
access_token = {
|
||||||
'name': str(user.username),
|
'name': str(user.username),
|
||||||
|
|
|
@ -25,10 +25,9 @@ from webauthn.helpers.structs import (
|
||||||
UserVerificationRequirement,
|
UserVerificationRequirement,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..model import db, User, Totp, AppToken, PasskeyCredential
|
from ..model import db, User, AppToken, PasskeyCredential
|
||||||
from ..form.frontend import ClientCertForm, TOTPForm, \
|
from ..form.frontend import ClientCertForm, PasswordChangeForm, \
|
||||||
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm, \
|
AppTokenForm, AppTokenDeleteForm, PasskeyRegisterForm
|
||||||
AppTokenForm, AppTokenDeleteForm
|
|
||||||
from ..form.base import ButtonForm
|
from ..form.base import ButtonForm
|
||||||
from ..auth_providers import PasswordAuthProvider
|
from ..auth_providers import PasswordAuthProvider
|
||||||
from .oauth2 import redirect_login, oauth2
|
from .oauth2 import redirect_login, oauth2
|
||||||
|
@ -188,43 +187,6 @@ def app_token_delete(app_token_name: str) -> ResponseReturnValue:
|
||||||
|
|
||||||
return redirect(url_for('frontend.app_token'))
|
return redirect(url_for('frontend.app_token'))
|
||||||
|
|
||||||
@frontend_views.route('/totp')
|
|
||||||
def totp() -> ResponseReturnValue:
|
|
||||||
delete_form = TOTPDeleteForm()
|
|
||||||
return render_template('frontend/totp.html.j2', delete_form=delete_form)
|
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/totp/new', methods=['GET', 'POST'])
|
|
||||||
def totp_new() -> ResponseReturnValue:
|
|
||||||
form = TOTPForm()
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
totp = Totp(name=form.data['name'], secret=form.data['secret'], user=get_current_user())
|
|
||||||
if totp.verify(form.data['token']):
|
|
||||||
get_current_user().totps.append(totp)
|
|
||||||
db.session.commit()
|
|
||||||
return jsonify({
|
|
||||||
'status': 'ok'})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'errors': [
|
|
||||||
'TOTP Token invalid'
|
|
||||||
]})
|
|
||||||
return render_template('frontend/totp_new.html.j2', form=form)
|
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/totp/<totp_name>/delete', methods=['GET', 'POST'])
|
|
||||||
def totp_delete(totp_name) -> ResponseReturnValue:
|
|
||||||
totp = Totp.query.filter(Totp.name == totp_name).first() # type: Optional[Totp]
|
|
||||||
db.session.delete(totp)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'status': 'ok'})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Passkey
|
## Passkey
|
||||||
|
|
||||||
@frontend_views.route('/passkey/list', methods=['GET'])
|
@frontend_views.route('/passkey/list', methods=['GET'])
|
||||||
|
@ -243,7 +205,7 @@ def passkey_new() -> ResponseReturnValue:
|
||||||
|
|
||||||
|
|
||||||
user = get_current_user() # type: User
|
user = get_current_user() # type: User
|
||||||
form = WebauthnRegisterForm()
|
form = PasskeyRegisterForm()
|
||||||
|
|
||||||
options = webauthn.generate_registration_options(
|
options = webauthn.generate_registration_options(
|
||||||
rp_name="Lenticluar Cloud",
|
rp_name="Lenticluar Cloud",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from authlib.integrations.flask_client import OAuth
|
from authlib.integrations.flask_client import OAuth
|
||||||
from authlib.integrations.base_client.errors import MismatchingStateError, OAuthError
|
from authlib.integrations.base_client.errors import MismatchingStateError, OAuthError
|
||||||
from flask import Flask, Blueprint, Response, session, request, redirect, url_for
|
from flask import Flask, Blueprint, current_app, session, request, redirect, url_for
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -29,7 +29,8 @@ login_manager = LoginManager()
|
||||||
def redirect_login() -> ResponseReturnValue:
|
def redirect_login() -> ResponseReturnValue:
|
||||||
logout_user()
|
logout_user()
|
||||||
session['next_url'] = request.path
|
session['next_url'] = request.path
|
||||||
redirect_uri = url_for('oauth2.authorized', _external=True)
|
public_url = current_app.config['PUBLIC_URL']
|
||||||
|
redirect_uri = public_url + url_for('oauth2.authorized')
|
||||||
response = oauth2.custom.authorize_redirect(redirect_uri)
|
response = oauth2.custom.authorize_redirect(redirect_uri)
|
||||||
if not isinstance(response, WerkzeugResponse):
|
if not isinstance(response, WerkzeugResponse):
|
||||||
raise RuntimeError("invalid redirect")
|
raise RuntimeError("invalid redirect")
|
||||||
|
|
Loading…
Reference in a new issue