update fixes, ...

master
TuxCoder 2022-07-15 10:53:06 +02:00
parent c6042973fe
commit 1947a6f24a
31 changed files with 3395 additions and 145 deletions

View File

@ -82,7 +82,7 @@ window.auth = {
submit: function(form) {
SimpleFormSubmit.submitForm(form.action, form)
.then(response =>{
response.json().then(function(data) {
response.json().then(data => {
if (data.errors) {
var msg ='<ul>';
for( var field in data.errors) {
@ -156,7 +156,9 @@ window.password_change= {
new Dialog('Password changed', 'Password changed successfully!').show();
}
});
});
}).error(error =>{
new Dialog('Password change Error', `Error Happend: ${msg}`).show()
});
return false;
}
@ -201,10 +203,10 @@ window.client_cert = {
SimpleFormSubmit.submitForm(form_sign_key.action, form_sign_key)
.then(response => {
response.json().then( response => {
if (data.errors) {
response.json().then( json_data => {
if (json_data.errors) {
var msg ='<ul>';
for( var field in data.errors) {
for( var field in json_data.repsonse) {
msg += `<li>${field}: ${data.errors[field]}</li>`;
}
msg += '</ul>';

194
index.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -36,12 +36,11 @@ LENTICULAR_CLOUD_SERVICES = {
'client_cert': True,
'pki_config':{
'email': '{username}@jabber.{domain}'
}
},
'app_token': True
},
'calendar': {
'client_cert': True
},
'mail': {
'client_cert': True
'mail-cardav': {
'client_cert': False,
'app_token': True
}
}

View File

@ -81,7 +81,7 @@ def cli_run(app: Flask, args) -> None:
print("running in debug mode")
logging.basicConfig(level=logging.DEBUG)
#app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1)
app.run(debug=False, host='127.0.0.1', port=5000)
app.run(debug=True, host='127.0.0.1', port=5000)
def cli_db_upgrade(args) -> None:

View File

@ -33,6 +33,12 @@ class TOTPForm(FlaskForm):
class TOTPDeleteForm(FlaskForm):
submit = SubmitField(gettext('Delete'))
class AppTokenForm(FlaskForm):
name = StringField(gettext('name'), validators=[DataRequired(),Length(min=1, max=255) ])
submit = SubmitField(gettext('Activate'))
class AppTokenDeleteForm(FlaskForm):
submit = SubmitField(gettext('Delete'))
class WebauthnRegisterForm(FlaskForm):
"""webauthn register token form"""

View File

@ -0,0 +1,48 @@
"""fix app token
Revision ID: 0f217e90cd07
Revises: 0518a8625b50
Create Date: 2022-06-18 23:24:12.687324
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0f217e90cd07'
down_revision = '0518a8625b50'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('app_token', sa.Column('user_id', sa.String(length=36), nullable=False))
op.add_column('app_token', sa.Column('last_used', sa.DateTime(), nullable=True))
op.create_foreign_key(None, 'app_token', 'user', ['user_id'], ['id'])
op.add_column('totp', sa.Column('last_used', sa.DateTime(), nullable=True))
tmp_table = sa.Table('_alembic_tmp_user', sa.MetaData())
op.execute(sa.schema.DropTable(tmp_table, if_exists=True))
with op.batch_alter_table('user') as batch_op:
batch_op.alter_column('enabled',
existing_type=sa.BOOLEAN(),
nullable=False,
existing_server_default=sa.text("'false'"))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
tmp_table = sa.Table('_alembic_tmp_user', sa.MetaData())
op.execute(sa.schema.DropTable(tmp_table, if_exists=True))
with op.batch_alter_table('user') as batch_op:
batch_op.alter_column('enabled',
existing_type=sa.BOOLEAN(),
nullable=True,
existing_server_default=sa.text("'false'"))
op.drop_column('totp', 'last_used')
op.drop_constraint(None, 'app_token', type_='foreignkey')
op.drop_column('app_token', 'last_used')
op.drop_column('app_token', 'user_id')
# ### end Alembic commands ###

View File

@ -9,6 +9,8 @@ import pyotp
import json
import logging
import crypt
import secrets
import string
from flask_sqlalchemy import SQLAlchemy, orm
from flask_migrate import Migrate
from datetime import datetime
@ -23,7 +25,7 @@ logger = logging.getLogger(__name__)
db = SQLAlchemy() # type: SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()
@ -42,6 +44,7 @@ class Service(object):
def __init__(self, name: str):
self._name = name
self._app_token = False
self._client_cert = False
self._pki_config = {
'cn': '{username}',
@ -53,6 +56,8 @@ class Service(object):
"""
"""
service = Service(name)
if 'app_token' in config:
service._app_token = bool(config['app_token'])
if 'client_cert' in config:
service._client_cert = bool(config['client_cert'])
if 'pki_config' in config:
@ -68,6 +73,10 @@ class Service(object):
def client_cert(self) -> bool:
return self._client_cert
@property
def app_token(self) -> bool:
return self._app_token
@property
def pki_config(self) -> dict[str,str]:
if not self._client_cert:
@ -148,6 +157,7 @@ class User(BaseModel):
enabled = db.Column(db.Boolean, nullable=False, default=False)
app_tokens = db.relationship('AppToken', back_populates='user')
totps = db.relationship('Totp', back_populates='user')
webauthn_credentials = db.relationship('WebauthnCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True)
@ -162,7 +172,7 @@ class User(BaseModel):
print(f'getitem: {key}') # TODO
@property
def groups(self) -> list[str]:
def groups(self) -> list['Group']:
if self.username == 'tuxcoder':
return [Group(name='admin')]
else:
@ -173,23 +183,44 @@ class User(BaseModel):
domain = current_app.config['DOMAIN']
return f'{self.username}@{domain}'
def change_password(self, password_new: str) -> bool:
password_hashed = crypt.crypt(password_new)
return True
def change_password(self, password_new: str) -> None:
self.password_hashed = crypt.crypt(password_new)
def get_tokens_by_service(self, service: Service) -> list['AppToken']:
return [ token for token in self.app_tokens if token.service_name == service.name ]
def get_token(self, service: Service, name: str) -> Optional['AppToken']:
for token in self.app_tokens:
if token.service_name == service.name and token.name == name:
return token # type: ignore
return None
class AppToken(BaseModel):
id = db.Column(db.Integer, primary_key=True)
service_name = db.Column(db.String, nullable=False)
user_id = db.Column(
db.String(length=36),
db.ForeignKey(User.id), nullable=False)
user = db.relationship(User)
token = db.Column(db.String, nullable=False)
name = db.Column(db.String, nullable=False)
last_used = db.Column(db.DateTime, nullable=True)
@staticmethod
def new(service: Service):
app_token = AppToken()
app_token.service_name = service.name
alphabet = string.ascii_letters + string.digits
app_token.token = ''.join(secrets.choice(alphabet) for i in range(12))
return app_token
class Totp(BaseModel):
id = db.Column(db.Integer, primary_key=True)
secret = db.Column(db.String, nullable=False)
name = db.Column(db.String, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
#last_used = db.Column(db.DateTime, nullable=True)
last_used = db.Column(db.DateTime, nullable=True)
user_id = db.Column(
db.String(length=36),

View File

@ -48,7 +48,7 @@ class Pki(object):
'''
pki_path: str base path from the pkis
'''
self._pki_path = Path(os.getcwd()) / app.config['PKI_PATH']
self._pki_path = Path(app.root_path) / app.config['PKI_PATH']
self._domain = app.config['DOMAIN']
@ -328,4 +328,4 @@ class Pki(object):
backend=default_backend())
return crl
pki = Pki()
pki = Pki()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,50 +4,48 @@
{% block content %}
{#
<ul class="nav nav-tabs" id="myTab" role="tablist">
{% for service in services.values() %}
{% for service in services.values() if service.app_token %}
<li class="nav-item">
<a class="nav-link{{' active' if loop.first else ''}}" id="home-tab" data-toggle="tab" href="#{{ service.name }}" role="tab" aria-controls="home" aria-selected="true">{{ service.name }}</a>
</li>
{% endfor %}
</ul>
<div class="tab-content" id="myTabContent">
{% for service in services.values() if service.client_cert %}
{% for service in services.values() if service.app_token %}
<div class="tab-pane fade{{ ' show active' if loop.first else '' }}" id="{{ service.name }}" role="tabpanel" aria-labelledby="{{ service.name }}-tab">
<table class="table">
<thead>
<tr>
<th>not valid before</th>
<th>not valid after</th>
<th>serial_number<th>
<th>name</th>
<th>last used</th>
<th>created at<th>
<th> <th>
</tr>
</thead>
<tbody>
{% for cert in client_certs[service.name] %}
<tr {{ 'class="table-warning"' if not cert.is_valid else ''}}>
<td>{{ cert.not_valid_before }}</td>
<td>{{ cert.not_valid_after }}</td>
<td>{{ cert.serial_number_hex }}</td>
{% for app_token in current_user.get_tokens_by_service(service) %}
<tr>
<td>{{ app_token.name }}</td>
<td>{{ app_token.last_used }}</td>
<td>{{ app_token.created_at }}</td>
<td>
<a title="{{ gettext('Download') }}" href="{{ url_for('.get_client_cert', service_name=service.name, serial_number=cert.serial_number_hex) }}"><i class="fas fa-file-download"></i></a>
&nbsp;
{% if cert.is_valid %}
<a title="{{ gettext('Revoke')}}" href="{{ url_for('.revoke_client_cert', service_name=service.name, serial_number=cert.serial_number_hex) }}" onclick="client_cert.revoke_certificate(this.href, '{{ cert.serial_number_hex }}'); return false;"><i class="fas fa-ban"></i></a>
{% endif %}
{{ render_form(delete_form, action_url=url_for('frontend.app_token_delete', service_name=service.name,app_token_name=app_token.name)) }}
{#
<a title="{{ gettext('Revoke')}}" href="{{ url_for('.app_token_revoce', id=app_token.id) }}" onclick="client_cert.revoke_certificate(this.href, '{{ cert.serial_number_hex }}'); return false;"><i class="fas fa-ban"></i></a>
#}
</td>
</tr>
{% endfor %}
</table>
<a class="btn btn-primary" href="{{ url_for('frontend.client_cert_new', service_name=service.name) }}">
New Certificate
</a>
</table>
<a class="btn btn-primary" href="{{ url_for('frontend.app_token_new', service_name=service.name) }}">
New Token
</a>
</div>
{% endfor %}
</div>
#}
{% endblock %}

View File

@ -1,44 +1,12 @@
{% extends 'frontend/base.html.j2' %}
{% block title %}{{ gettext('new client cert - {service_name}').format(service_name=service.name) }}{% endblock %}
{% block title %}{{ gettext('new app token for {service_name}').format(service_name=service.name) }}{% endblock %}
{% block content %}
<div id="sign-key">
<h4>Sign Public Key</h4>
<div>
{{ render_form(form) }}
</div>
<div id="gen-key">
<h4>Generate new key in the browser</h4>
<div id="gen-key-sign" style="display: none">
{{ render_form(form) }}
</div>
<form id="gen-key-form">
<div class="form-group">
<label for="valid_time" class="control-label ">Key Password for .p12 (optional)</label>
<div class="">
<input class="form-control" id="cert-password" type="password" name="password"/>
</div>
</div>
<div class="form-group">
<label for="valid_time" class="control-label ">Key Size</label>
<div class="">
<select id="key-size" class="custom-select">
<option value="4096" selected>4096</option>
<option value="2048">2048</option>
</select>
</div>
</div>
<div class="form-group ">
<label for="valid_time" class="control-label ">valid time in days</label>
<div class="">
<input class="form-control" name="valid_time" required type="text" value="365">
</div>
</div>
</form>
<button id="generate-key" class="btn btn-primary" onclick="client_cert.generate_private_key()">Generate Key</button>
<a style="display: none" id="save-button" download="lenticular_cloud_{{ service.name }}.p12" class="btn btn-primary">Save Keypair</a>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'frontend/base.html.j2' %}
{% block title %}{{ gettext('new app token for {service_name}').format(service_name=service.name) }}{% endblock %}
{% block content %}
<div>
<p>
Your new App Token for {{ service.name }}:
</p>
<p>
<code id="app_token_secret">{{ app_token.token }}</code><br />
</p>
<p>
<button class="btn btn-primary" onclick="navigator.clipboard.writeText(document.getElementById('app_token_secret').textContent);">
<i class="fa-solid fa-clipboard"></i>
Copy Secret
</button>
</p>
</div>
{% endblock %}

View File

@ -6,7 +6,7 @@
<ul class="nav nav-tabs" id="myTab" role="tablist">
{% for service in services.values() %}
{% for service in services.values() if service.client_cert %}
<li class="nav-item">
<a class="nav-link{{' active' if loop.first else ''}}" id="home-tab" data-toggle="tab" href="#{{ service.name }}" role="tab" aria-controls="home" aria-selected="true">{{ service.name }}</a>
</li>

View File

@ -9,6 +9,7 @@ from authlib.integrations.base_client.errors import InvalidTokenError
from ory_hydra_client.api.admin import list_o_auth_2_clients, get_o_auth_2_client, update_o_auth_2_client, create_o_auth_2_client
from ory_hydra_client.models import OAuth2Client, GenericError
from typing import Optional
from collections.abc import Iterable
import logging
from ..model import db, User
@ -44,19 +45,19 @@ async def index() -> ResponseReturnValue:
@admin_views.route('/user', methods=['GET'])
async def users():
users = User.query.all()
users = User.query.all() # type: Iterable[User]
return render_template('admin/users.html.j2', users=users)
@admin_views.route('/registrations', methods=['GET'])
def registrations() -> ResponseReturnValue:
users = User.query.filter_by(enabled=False).all()
users = User.query.filter_by(enabled=False).all() # type: Iterable[User]
return render_template('admin/registrations.html.j2', users=users)
@admin_views.route('/registration/<registration_id>', methods=['DELETE'])
def registration_delete(registration_id) -> ResponseReturnValue:
user = User.query.get(registration_id)
user = User.query.get(registration_id) # type: Optional[User]
if user is None:
return jsonify({}), 404
db.session.delete(user)
@ -66,7 +67,9 @@ def registration_delete(registration_id) -> ResponseReturnValue:
@admin_views.route('/registration/<registration_id>', methods=['PUT'])
def registration_accept(registration_id) -> ResponseReturnValue:
user = User.query.get(registration_id)
user = User.query.get(registration_id) # type: Optional[User]
if user is None:
return jsonify({'message':'user not found'}), 404
user.enabled = True
db.session.commit()
return jsonify({})

View File

@ -7,11 +7,15 @@ from flask.templating import render_template
from flask.typing import ResponseReturnValue
from flask import Blueprint, render_template, request, url_for
from datetime import datetime
from typing import Optional, Any
import logging
import httpx
import secrets
from ..model import User
from ..model import db, User
from ..hydra import hydra_service
from ..lenticular_services import lenticular_services
from ory_hydra_client.api.admin import introspect_o_auth_2_token
from ory_hydra_client.models import GenericError
@ -40,39 +44,50 @@ def user_list() -> ResponseReturnValue:
@api_views.route('/introspect', methods=['POST'])
def introspect() -> ResponseReturnValue:
token = request.form['token']
logger.error(f'debug token: {token}')
token = request.form['token'] # type: Optional[str]
resp = httpx.post("https://hydra.cloud.tux.ac/oauth2/introspect", data={'token':token})
#if token_info is None or isinstance(token_info, GenericError):
if resp.status_code != 200:
return jsonify({}), 500
token_info = resp.json()
#token_info = introspect_o_auth_2_token.sync(_client=hydra_service, token=token)
if not token_info['active']:
return jsonify({'active': False})
token_info['email'] = token_info['ext']['email']
logger.error(f'debug: {token_info}')
return jsonify(token_info)
@api_views.route('email/login', methods=['POST'])
def email_login() -> ResponseReturnValue:
logger.error(f'{request}')
logger.error(f'{request.headers}')
@api_views.route('/login/<service_name>', methods=['POST'])
def email_login(service_name: str) -> ResponseReturnValue:
if service_name not in lenticular_services:
return '', 404
service = lenticular_services[service_name]
if not request.is_json:
return jsonify({}), 400
req_payload = request.get_json()
logger.debug(f'{req_payload}')
req_payload = request.get_json() # type: Any
if not isinstance(req_payload, dict):
return 'bad request', 400
password = req_payload["password"]
username = req_payload["username"]
if password == "123456":
return jsonify({})
if '@' in username:
username = username.split('@')[0]
user = User.query.filter_by(username=username.lower()).first() # type: Optional[User]
if user is None:
logger.warning(f'login with invalid username')
return jsonify({}), 403
for app_token in user.get_tokens_by_service(service):
if secrets.compare_digest(password, app_token.token):
app_token.last_used = datetime.now()
db.session.commit()
return jsonify({'username': user.username}), 200
logger.warning(f'login with invalid password for {username}')
return jsonify({}), 403

View File

@ -60,7 +60,9 @@ async def consent() -> ResponseReturnValue:
requested_audiences = consent_request.requested_access_token_audience
if form.validate_on_submit() or consent_request.skip:
user = User.query.get(consent_request.subject)
user = User.query.get(consent_request.subject) # type: Optional[User]
if user is None:
return 'internal error', 500
token_data = {
'name': str(user.username),
'preferred_username': str(user.username),
@ -118,7 +120,7 @@ async def login() -> ResponseReturnValue:
return redirect(resp.redirect_to)
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.data['name']).first()
user = User.query.filter_by(username=form.data['name']).first() # type: Optional[User]
if user:
session['username'] = str(user.username)
else:
@ -141,7 +143,7 @@ async def login_auth() -> ResponseReturnValue:
if 'username' not in session:
return redirect(url_for('auth.login'))
auth_forms = {}
user = User.query.filter_by(username=session['username']).first()
user = User.query.filter_by(username=session['username']).first() # Optional[User]
for auth_provider in AUTH_PROVIDER_LIST:
form = auth_provider.get_form()
if auth_provider.get_name() not in session['auth_providers'] and\
@ -178,7 +180,7 @@ async def login_auth() -> ResponseReturnValue:
def webauthn_pkcro_route():
"""login webauthn pkcro route"""
user = User.query.filter(User.id == session.get('webauthn_login_user_id')).one_or_none()
user = User.query.filter(User.id == session.get('webauthn_login_user_id')).one() #type: User
form = ButtonForm()
if user and form.validate_on_submit():
pkcro, state = webauthn.authenticate_begin(webauthn_credentials(user))

View File

@ -21,11 +21,13 @@ from ory_hydra_client.models import GenericError
from urllib.parse import urlencode, parse_qs
from random import SystemRandom
import string
from collections.abc import Iterable
from typing import Optional
from ..model import db, User, SecurityUser, Totp, WebauthnCredential
from ..model import db, User, SecurityUser, Totp, AppToken, WebauthnCredential
from ..form.frontend import ClientCertForm, TOTPForm, \
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm, \
AppTokenForm, AppTokenDeleteForm
from ..form.base import ButtonForm
from ..auth_providers import PasswordAuthProvider
from .auth import webauthn
@ -111,6 +113,8 @@ def revoke_client_cert(service_name, serial_number) -> ResponseReturnValue:
'/client_cert/<service_name>/new',
methods=['GET', 'POST'])
def client_cert_new(service_name) -> ResponseReturnValue:
if service_name not in lenticular_services:
return '', 404
service = lenticular_services[service_name]
form = ClientCertForm()
if form.validate_on_submit():
@ -139,16 +143,51 @@ def client_cert_new(service_name) -> ResponseReturnValue:
@frontend_views.route('/app_token')
def app_token() -> ResponseReturnValue:
delete_form = TOTPDeleteForm()
return render_template('frontend/app_token.html.j2', delete_form=delete_form)
delete_form = AppTokenDeleteForm()
form = ClientCertForm()
return render_template('frontend/app_token.html.j2',
delete_form=delete_form,
services=lenticular_services)
@frontend_views.route('/app_token/<service_name>/new')
@frontend_views.route('/app_token/<service_name>/new', methods=['GET','POST'])
def app_token_new(service_name: str) -> ResponseReturnValue:
return
if service_name not in lenticular_services:
return '', 404
service = lenticular_services[service_name]
form = AppTokenForm()
@frontend_views.route('/app_token/<service_name>/<token_name>')
def app_token_delete(service_name: str, token_name: str) -> ResponseReturnValue:
return
if form.validate_on_submit():
app_token = AppToken.new(service)
form.populate_obj(app_token)
# check for duplicate names
for user_app_token in current_user.app_tokens:
if user_app_token.name == app_token.name:
return 'name already exist', 400
current_user.app_tokens.append(app_token)
db.session.commit()
return render_template('frontend/app_token_new_show.html.j2', service=service, app_token=app_token)
return render_template('frontend/app_token_new.html.j2',
form=form,
service=service)
@frontend_views.route('/app_token/<service_name>/<app_token_name>', methods=["POST"])
def app_token_delete(service_name: str, app_token_name: str) -> ResponseReturnValue:
form = AppTokenDeleteForm()
if service_name not in lenticular_services:
return '', 404
service = lenticular_services[service_name]
if form.validate_on_submit():
app_token = current_user.get_token(service, app_token_name)
if app_token is None:
return 'not found', 404
db.session.delete(app_token)
db.session.commit()
return redirect(url_for('frontend.app_token'))
@frontend_views.route('/totp')
def totp() -> ResponseReturnValue:
@ -178,7 +217,7 @@ def totp_new() -> ResponseReturnValue:
@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()
totp = Totp.query.filter(Totp.name == totp_name).first() # type: Optional[Totp]
db.session.delete(totp)
db.session.commit()
@ -190,7 +229,7 @@ def totp_delete(totp_name) -> ResponseReturnValue:
def webauthn_list_route() -> ResponseReturnValue:
"""list registered credentials for current user"""
creds = WebauthnCredential.query.all()
creds = WebauthnCredential.query.all() # type: Iterable[WebauthnCredential]
return render_template('frontend/webauthn_list.html', creds=creds, button_form=ButtonForm())
@ -200,7 +239,7 @@ def webauthn_delete_route(webauthn_id: str) -> ResponseReturnValue:
form = ButtonForm()
if form.validate_on_submit():
cred = WebauthnCredential.query.filter(WebauthnCredential.id == webauthn_id).one()
cred = WebauthnCredential.query.filter(WebauthnCredential.id == webauthn_id).one() # type: WebauthnCredential
db.session.delete(cred)
db.session.commit()
return redirect(url_for('app.webauthn_list_route'))
@ -223,7 +262,9 @@ def random_string(length=32) -> str:
def webauthn_pkcco_route() -> ResponseReturnValue:
"""get publicKeyCredentialCreationOptions"""
user = User.query.get(current_user.id)
user = User.query.get(current_user.id) #type: Optional[User]
if user is None:
return 'internal error', 500
user_handle = random_string()
exclude_credentials = webauthn_credentials(user)
pkcco, state = webauthn.register_begin(
@ -238,7 +279,7 @@ def webauthn_pkcco_route() -> ResponseReturnValue:
def webauthn_register_route() -> ResponseReturnValue:
"""register credential for current user"""
user = User.query.get(current_user.id)
user = current_user # type: User
form = WebauthnRegisterForm()
if form.validate_on_submit():
try:
@ -279,11 +320,11 @@ def password_change_post() -> ResponseReturnValue:
current_user, password_old):
return jsonify(
{'errors': {'password_old': 'Old Password is invalid'}})
resp = current_user.change_password(password_new)
if resp:
return jsonify({})
else:
return jsonify({'errors': {'internal': 'internal server errror'}})
current_user.change_password(password_new)
logger.info(f"user {current_user.username} changed password")
db.session.commit()
return jsonify({})
return jsonify({'errors': form.errors})

View File

@ -5,6 +5,7 @@ from flask_login import login_user, logout_user, current_user
from flask.typing import ResponseReturnValue
from flask_login import LoginManager
from typing import Optional
from werkzeug.wrappers.response import Response as WerkzeugResponse
import logging
from ..model import User, SecurityUser
@ -28,7 +29,7 @@ def redirect_login() -> ResponseReturnValue:
session['next_url'] = request.path
redirect_uri = url_for('oauth2.authorized', _external=True)
response = oauth2.custom.authorize_redirect(redirect_uri)
if isinstance(response, Response):
if not isinstance(response, WerkzeugResponse):
raise RuntimeError("invalid redirect")
return response
@ -44,7 +45,7 @@ def authorized() -> ResponseReturnValue:
return 'bad request', 400
session['token'] = token
userinfo = oauth2.custom.get('/userinfo').json()
user = User.query.get(str(userinfo["sub"]))
user = User.query.get(str(userinfo["sub"])) # type: Optional[User]
if user is None:
return "user not found", 404
logger.info(f"user `{user.username}` successfully logged in")
@ -60,14 +61,14 @@ def authorized() -> ResponseReturnValue:
def login() -> ResponseReturnValue:
redirect_uri = url_for('.authorized', _external=True)
response = oauth2.custom.authorize_redirect(redirect_uri)
#if type(response) != Response:
# raise RuntimeError("invalid redirect")
if not isinstance(response, WerkzeugResponse):
raise RuntimeError("invalid redirect")
return response
@login_manager.user_loader
def user_loader(username) -> Optional[User]:
user = User.query.filter_by(username=username).first()
user = User.query.filter_by(username=username).first() # type: Optional[User]
if isinstance(user, User):
return user
else:

2953
package-lock.json generated

File diff suppressed because it is too large Load Diff