add oauth2 token managment, partial password change, bugfixes
This commit is contained in:
		
							parent
							
								
									6c388c8129
								
							
						
					
					
						commit
						6334c993a9
					
				
					 19 changed files with 236 additions and 137 deletions
				
			
		| 
						 | 
				
			
			@ -77,6 +77,27 @@ window.fido2 = {
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.password_change= {
 | 
			
		||||
	init: function(){
 | 
			
		||||
		var form = $('form');
 | 
			
		||||
		SimpleFormSubmit.submitForm(form.action, form)
 | 
			
		||||
			.then(response =>{
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.oauth2_token = {
 | 
			
		||||
	revoke: function(href, id){
 | 
			
		||||
		var dialog = new ConfirmDialog(`Are you sure to revoke all tokens from client "${id}"?`);
 | 
			
		||||
		dialog.show().then(()=>{
 | 
			
		||||
			fetch(href, {
 | 
			
		||||
				method: 'DELETE'
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
window.client_cert = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
from flask import current_app
 | 
			
		||||
from .form.login import PasswordForm, TotpForm, Fido2Form
 | 
			
		||||
from .form.auth import PasswordForm, TotpForm, Fido2Form
 | 
			
		||||
from ldap3 import Server, Connection
 | 
			
		||||
from ldap3.core.exceptions import LDAPException
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ from flask_wtf import FlaskForm
 | 
			
		|||
from wtforms import StringField, SubmitField, TextField, \
 | 
			
		||||
        TextAreaField, PasswordField, IntegerField, FloatField, \
 | 
			
		||||
        DateTimeField, DateField, FormField, BooleanField, \
 | 
			
		||||
        SelectField, Form as NoCsrfForm
 | 
			
		||||
        SelectField, Form as NoCsrfForm, SelectMultipleField
 | 
			
		||||
from wtforms.widgets.html5 import NumberInput, DateInput
 | 
			
		||||
from wtforms.validators import DataRequired, NumberRange, Optional, NoneOf, Length
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
| 
						 | 
				
			
			@ -27,3 +27,11 @@ class TotpForm(FlaskForm):
 | 
			
		|||
class Fido2Form(FlaskForm):
 | 
			
		||||
    fido2 = TextField(gettext('Fido2'), default="Javascript Required")
 | 
			
		||||
    submit = SubmitField(gettext('Authorize'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConsentForm(FlaskForm):
 | 
			
		||||
#   scopes = SelectMultipleField(gettext('scopes'))
 | 
			
		||||
#   audiences = SelectMultipleField(gettext('audiences'))
 | 
			
		||||
    remember = BooleanField(gettext('remember me'))
 | 
			
		||||
    submit = SubmitField()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ from wtforms import StringField, SubmitField, TextField, \
 | 
			
		|||
        SelectField, Form as NoCsrfForm, HiddenField
 | 
			
		||||
from wtforms.widgets.html5 import NumberInput, DateInput
 | 
			
		||||
from wtforms.validators import DataRequired, NumberRange, \
 | 
			
		||||
        Optional, NoneOf, Length
 | 
			
		||||
        Optional, NoneOf, Length, EqualTo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientCertForm(FlaskForm):
 | 
			
		||||
| 
						 | 
				
			
			@ -34,5 +34,11 @@ class TOTPDeleteForm(FlaskForm):
 | 
			
		|||
    submit = SubmitField(gettext('Delete'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordChangeForm(FlaskForm):
 | 
			
		||||
    old_password = PasswordField(gettext('Old Password'), validators=[DataRequired()])
 | 
			
		||||
    password = PasswordField(gettext('New Password'), validators=[DataRequired()])
 | 
			
		||||
    password_repeat = PasswordField(gettext('Repeat Password'), validators=[DataRequired(),EqualTo('password')])
 | 
			
		||||
    submit = SubmitField(gettext('Change Password'))
 | 
			
		||||
 | 
			
		||||
class OidcAuthenticationConfirm(FlaskForm):
 | 
			
		||||
    submit = SubmitField(gettext('Continue'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,24 +6,12 @@ from flask.helpers import make_response
 | 
			
		|||
from flask.templating import render_template
 | 
			
		||||
from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
 | 
			
		||||
 | 
			
		||||
from pyop.access_token import AccessToken, BearerTokenError
 | 
			
		||||
from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
 | 
			
		||||
    InvalidSubjectIdentifier, InvalidClientRegistrationRequest
 | 
			
		||||
from pyop.util import should_fragment_encode
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, render_template, request, url_for
 | 
			
		||||
from flask_login import login_required, login_user, logout_user
 | 
			
		||||
from werkzeug.utils import redirect
 | 
			
		||||
import logging
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
from base64 import b64decode, b64encode
 | 
			
		||||
import ory_hydra_client as hydra
 | 
			
		||||
from requests_oauthlib.oauth2_session import OAuth2Session
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from ..model import User, SecurityUser
 | 
			
		||||
from ..model import User
 | 
			
		||||
from ..model_db import User as DbUser
 | 
			
		||||
from ..form.login import LoginForm
 | 
			
		||||
from ..auth_providers import LdapAuthProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +25,9 @@ def userinfo():
 | 
			
		|||
    user_db = DbUser.query.get(token_info.sub)
 | 
			
		||||
    user = User.query().by_username(user_db.username)
 | 
			
		||||
 | 
			
		||||
    public_url = current_app.config.get('HYDRA_PUBLIC_URL')
 | 
			
		||||
    r = requests.get(
 | 
			
		||||
            "http://127.0.0.1:4444/userinfo",
 | 
			
		||||
            f"{public_url}/userinfo",
 | 
			
		||||
            headers={
 | 
			
		||||
                'authorization': request.headers['authorization']})
 | 
			
		||||
    userinfo = r.json()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,19 +4,11 @@ from urllib.parse import urlencode, parse_qs
 | 
			
		|||
import flask
 | 
			
		||||
from flask import Blueprint, redirect
 | 
			
		||||
from flask import current_app, session
 | 
			
		||||
from flask import jsonify
 | 
			
		||||
from flask.helpers import make_response
 | 
			
		||||
from flask.templating import render_template
 | 
			
		||||
from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
 | 
			
		||||
from flask_babel import gettext
 | 
			
		||||
 | 
			
		||||
from pyop.access_token import AccessToken, BearerTokenError
 | 
			
		||||
from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
 | 
			
		||||
    InvalidSubjectIdentifier, InvalidClientRegistrationRequest
 | 
			
		||||
from pyop.util import should_fragment_encode
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, render_template, request, url_for
 | 
			
		||||
from flask import request, url_for
 | 
			
		||||
from flask_login import login_required, login_user, logout_user
 | 
			
		||||
from werkzeug.utils import redirect
 | 
			
		||||
import logging
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
from base64 import b64decode, b64encode
 | 
			
		||||
| 
						 | 
				
			
			@ -24,35 +16,48 @@ import http
 | 
			
		|||
 | 
			
		||||
from ..model import User, SecurityUser
 | 
			
		||||
from ..model_db import db, User as DbUser
 | 
			
		||||
from ..form.login import LoginForm
 | 
			
		||||
from ..form.auth import ConsentForm, LoginForm
 | 
			
		||||
from ..auth_providers import AUTH_PROVIDER_LIST
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
auth_views = Blueprint('auth', __name__, url_prefix='/auth')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_views.route('/consent', methods=['GET', 'POST'])
 | 
			
		||||
def consent():
 | 
			
		||||
    """Always grant consent."""
 | 
			
		||||
    # DUMMPY ONLY
 | 
			
		||||
 | 
			
		||||
    remember_me = True
 | 
			
		||||
    remember_for = 60*60*24*7 # remember for 7 days
 | 
			
		||||
    form = ConsentForm()
 | 
			
		||||
    remember_for = 60*60*24*7  # remember for 7 days
 | 
			
		||||
 | 
			
		||||
    consent_request = current_app.hydra_api.get_consent_request(request.args['consent_challenge'])
 | 
			
		||||
    consent_request = current_app.hydra_api.get_consent_request(
 | 
			
		||||
                                request.args['consent_challenge'])
 | 
			
		||||
    requested_scope = consent_request.requested_scope
 | 
			
		||||
    resp = current_app.hydra_api.accept_consent_request(consent_request.challenge, body={
 | 
			
		||||
        'grant_scope': requested_scope,
 | 
			
		||||
        'remember': remember_me,
 | 
			
		||||
        'remember_for': remember_for,
 | 
			
		||||
        })
 | 
			
		||||
    return redirect(resp.redirect_to)
 | 
			
		||||
    requested_audiences = consent_request.requested_access_token_audience
 | 
			
		||||
 | 
			
		||||
    if form.validate_on_submit() or consent_request.skip:
 | 
			
		||||
        resp = current_app.hydra_api.accept_consent_request(
 | 
			
		||||
            consent_request.challenge, body={
 | 
			
		||||
                'grant_scope': requested_scope,
 | 
			
		||||
                'grant_access_token_audience': requested_audiences,
 | 
			
		||||
                'remember': form.data['remember'],
 | 
			
		||||
#                'remember_for': remember_for,
 | 
			
		||||
            })
 | 
			
		||||
        return redirect(resp.redirect_to)
 | 
			
		||||
    return render_template(
 | 
			
		||||
            'auth/consent.html.j2',
 | 
			
		||||
            form=form,
 | 
			
		||||
            client=consent_request.client,
 | 
			
		||||
            requested_scope=requested_scope,
 | 
			
		||||
            requested_audiences=requested_audiences)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_views.route('/login', methods=['GET', 'POST'])
 | 
			
		||||
def login():
 | 
			
		||||
    login_challenge = request.args.get('login_challenge')
 | 
			
		||||
    login_request = current_app.hydra_api.get_login_request(login_challenge)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if login_request.skip:
 | 
			
		||||
        resp = current_app.hydra_api.accept_login_request(
 | 
			
		||||
            login_challenge,
 | 
			
		||||
| 
						 | 
				
			
			@ -66,8 +71,9 @@ def login():
 | 
			
		|||
        else:
 | 
			
		||||
            session['user'] = None
 | 
			
		||||
        session['auth_providers'] = []
 | 
			
		||||
        return redirect(url_for('auth.login_auth', login_challenge=login_challenge))
 | 
			
		||||
    return render_template('frontend/login.html.j2', form=form)
 | 
			
		||||
        return redirect(
 | 
			
		||||
                url_for('auth.login_auth', login_challenge=login_challenge))
 | 
			
		||||
    return render_template('auth/login.html.j2', form=form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_views.route('/login/auth', methods=['GET', 'POST'])
 | 
			
		||||
| 
						 | 
				
			
			@ -114,7 +120,7 @@ def login_auth():
 | 
			
		|||
        except TypeError:
 | 
			
		||||
            _next = None
 | 
			
		||||
        return redirect(_next or url_for('frontend.index'))
 | 
			
		||||
    return render_template('frontend/login_auth.html.j2', forms=auth_forms)
 | 
			
		||||
    return render_template('auth/login_auth.html.j2', forms=auth_forms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_views.route("/logout")
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +128,7 @@ def logout():
 | 
			
		|||
    logout_challenge = request.args.get('logout_challenge')
 | 
			
		||||
    logout_request = current_app.hydra_api.get_logout_request(logout_challenge)
 | 
			
		||||
    resp = current_app.hydra_api.accept_logout_request(logout_challenge)
 | 
			
		||||
    logout_user()
 | 
			
		||||
    return redirect(resp.redirect_to)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,8 +27,7 @@ from flask_dance.consumer import OAuth2ConsumerBlueprint
 | 
			
		|||
 | 
			
		||||
from ..model import User, SecurityUser, Totp
 | 
			
		||||
from ..model_db import OAuth, db, User as DbUser
 | 
			
		||||
from ..form.login import LoginForm
 | 
			
		||||
from ..form.frontend import ClientCertForm, TOTPForm, TOTPDeleteForm
 | 
			
		||||
from ..form.frontend import ClientCertForm, TOTPForm, TOTPDeleteForm, PasswordChangeForm
 | 
			
		||||
from ..auth_providers import AUTH_PROVIDER_LIST
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -218,3 +217,42 @@ def totp_delete(totp_name):
 | 
			
		|||
 | 
			
		||||
    return jsonify({
 | 
			
		||||
            'status': 'ok'})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@frontend_views.route('/password_change')
 | 
			
		||||
@login_required
 | 
			
		||||
def password_change():
 | 
			
		||||
 | 
			
		||||
    form = PasswordChangeForm()
 | 
			
		||||
    return render_template('frontend/password_change.html.j2', form=form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@frontend_views.route('/password_change', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def password_change_post():
 | 
			
		||||
    form = PasswordChangeForm()
 | 
			
		||||
    if form.validate():
 | 
			
		||||
        return jsonify({})
 | 
			
		||||
    return jsonify({'errors': form.errors})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@frontend_views.route('/oauth2_token')
 | 
			
		||||
@login_required
 | 
			
		||||
def oauth2_tokens():
 | 
			
		||||
 | 
			
		||||
    subject = current_app.oauth.session.get('/userinfo').json()['sub']
 | 
			
		||||
    consent_sessions = current_app.hydra_api.list_subject_consent_sessions(
 | 
			
		||||
                                subject)
 | 
			
		||||
 | 
			
		||||
    return render_template('frontend/oauth2_tokens.html.j2', consent_sessions=consent_sessions)
 | 
			
		||||
 | 
			
		||||
@frontend_views.route('/oauth2_token/<client_id>', methods=['DELETE'])
 | 
			
		||||
@login_required
 | 
			
		||||
def oauth2_token_revoke(client_id: str):
 | 
			
		||||
    subject = current_app.oauth.session.get('/userinfo').json()['sub']
 | 
			
		||||
    current_app.hydra_api.revoke_consent_sessions(
 | 
			
		||||
                                subject,
 | 
			
		||||
                                client=client_id)
 | 
			
		||||
 | 
			
		||||
    return jsonify({})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,35 +1,10 @@
 | 
			
		|||
import flask
 | 
			
		||||
from flask import Blueprint, redirect, request
 | 
			
		||||
from flask import current_app, session
 | 
			
		||||
from flask import jsonify
 | 
			
		||||
from flask.helpers import make_response
 | 
			
		||||
from flask.templating import render_template
 | 
			
		||||
from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
 | 
			
		||||
 | 
			
		||||
from pyop.access_token import AccessToken, BearerTokenError
 | 
			
		||||
from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
 | 
			
		||||
    InvalidSubjectIdentifier, InvalidClientRegistrationRequest
 | 
			
		||||
from pyop.util import should_fragment_encode
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, render_template, request, url_for
 | 
			
		||||
from flask_login import login_required, login_user, logout_user
 | 
			
		||||
from werkzeug.utils import redirect
 | 
			
		||||
import logging
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
from base64 import b64decode, b64encode
 | 
			
		||||
import ory_hydra_client as hydra
 | 
			
		||||
from requests_oauthlib.oauth2_session import OAuth2Session
 | 
			
		||||
import requests
 | 
			
		||||
from flask import current_app, Blueprint
 | 
			
		||||
from cryptography.hazmat.primitives import serialization
 | 
			
		||||
 | 
			
		||||
from ..model import User, SecurityUser
 | 
			
		||||
from ..model_db import User as DbUser
 | 
			
		||||
from ..form.login import LoginForm
 | 
			
		||||
from ..auth_providers import LdapAuthProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pki_views = Blueprint('pki', __name__, url_prefix='/')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pki_views.route('/<service_name>.crl')
 | 
			
		||||
def crl(service_name: str):
 | 
			
		||||
    service = current_app.lenticular_services[service_name]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ flask_babel
 | 
			
		|||
flask_wtf
 | 
			
		||||
flask_login
 | 
			
		||||
flask_sqlalchemy
 | 
			
		||||
flask_dancer
 | 
			
		||||
 | 
			
		||||
ldap3
 | 
			
		||||
ldap3_orm
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										45
									
								
								templates/auth/base.html.j2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								templates/auth/base.html.j2
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
{% extends 'base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
 | 
			
		||||
	<div class="col-xs-1"><button id="sidebarCollapse" class="btn btn-primary d-xs-block d-md-none" ><i class="fa fa-bars fa-2x"></i></button></div>
 | 
			
		||||
	<div class="col-xs-11"><a class="navbar-brand col-xs-11 col-sm-3 col-md-2 mr-0" href="/">Lenticular Cloud</a></div>
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
<div style="height: 40px"></div>
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
	<div class="row">
 | 
			
		||||
		{% for message in get_flashed_messages() %}
 | 
			
		||||
		<div class="alert alert-warning">
 | 
			
		||||
			<button type="button" class="close" data-dismiss="alert">×</button>
 | 
			
		||||
			{{ message }}
 | 
			
		||||
		</div>
 | 
			
		||||
		{% endfor %}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="row">
 | 
			
		||||
		{% if current_user.is_authenticated %}
 | 
			
		||||
		<nav class="col-md-2 d-none d-md-block bg-light sidebar fixed-top">
 | 
			
		||||
			<div class="sidebar-sticky active">
 | 
			
		||||
				{#<a href="/"><img alt="logo" class="container-fluid" src="/static/images/dog_main_small.png"></a>#}
 | 
			
		||||
				<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.index') }}">{{ gettext('Account') }}</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.totp') }}">{{ gettext('2FA - TOTP') }}</a></li>
 | 
			
		||||
				<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a></li>
 | 
			
		||||
			</div>
 | 
			
		||||
		</nav>
 | 
			
		||||
		{% endif %}
 | 
			
		||||
 | 
			
		||||
		<main class="col-md-9 ml-sm-auto col-lg-10 px-4" role="main">
 | 
			
		||||
			<h1>{% block title %}{% endblock %}</h1>
 | 
			
		||||
			<div class="card">
 | 
			
		||||
				<div class="card-body mt-5 mb-5">
 | 
			
		||||
					<div class="tab-content">
 | 
			
		||||
						{% block content %}{% endblock %}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</main>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										14
									
								
								templates/auth/consent.html.j2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								templates/auth/consent.html.j2
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
{% extends 'auth/base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ gettext('Consent') }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
	<p>
 | 
			
		||||
	The application "{{ client.client_id }}" requested the following scopes: {{ requested_scope }}
 | 
			
		||||
	</p>
 | 
			
		||||
	<p> Allow this app to access that data?</p>
 | 
			
		||||
 | 
			
		||||
	{{ render_form(form) }}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
{% extends 'frontend/base.html.j2' %}
 | 
			
		||||
{% extends 'auth/base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ gettext('Login') }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
{% extends 'frontend/base.html.j2' %}
 | 
			
		||||
{% extends 'auth/base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ gettext('Login') }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,62 +1,8 @@
 | 
			
		|||
{% extends 'base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% macro show_categories(categories) -%}
 | 
			
		||||
	<li>
 | 
			
		||||
		{% for category in categories %}
 | 
			
		||||
		<ul>
 | 
			
		||||
			<a href="{{ url_for('.sorts', category_id=category.id) }}">{{ category.name_de }}</a>
 | 
			
		||||
			{% if category.children %}
 | 
			
		||||
			{{ show_categories(category.children) }}
 | 
			
		||||
			{% endif %}
 | 
			
		||||
		</ul>
 | 
			
		||||
		{% endfor %}
 | 
			
		||||
	</li>
 | 
			
		||||
{%- endmacro %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
 | 
			
		||||
<div class='messages-box'>
 | 
			
		||||
</div>
 | 
			
		||||
<template id='confirm-dialog-template'>
 | 
			
		||||
	<div class="modal-dialog">
 | 
			
		||||
		<div class="modal-content">
 | 
			
		||||
 | 
			
		||||
			<div class="modal-header">
 | 
			
		||||
				<h5 class="modal-title"></h5>
 | 
			
		||||
				<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-body">
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-footer">
 | 
			
		||||
				<button type="button" class="btn btn-danger close" data-dismiss="modal">Cancel</button>
 | 
			
		||||
				<button type="button" class="btn btn-primary btn-ok process">Process</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
 | 
			
		||||
	<div class="modal-dialog">
 | 
			
		||||
		<div class="modal-content">
 | 
			
		||||
 | 
			
		||||
			<div class="modal-header">
 | 
			
		||||
				<h5 class="modal-title"></h5>
 | 
			
		||||
				<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-body">
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-footer">
 | 
			
		||||
				<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
 | 
			
		||||
				<a class="btn btn-danger btn-ok">Process</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
 | 
			
		||||
	<div class="col-xs-1"><button id="sidebarCollapse" class="btn btn-primary d-xs-block d-md-none" ><i class="fa fa-bars fa-2x"></i></button></div>
 | 
			
		||||
	<div class="col-xs-11"><a class="navbar-brand col-xs-11 col-sm-3 col-md-2 mr-0" href="/">Lenticular Cloud</a></div>
 | 
			
		||||
| 
						 | 
				
			
			@ -74,17 +20,17 @@
 | 
			
		|||
		{% endfor %}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="row">
 | 
			
		||||
		{% if current_user.is_authenticated %}
 | 
			
		||||
		<nav class="col-md-2 d-none d-md-block bg-light sidebar fixed-top">
 | 
			
		||||
			<div class="sidebar-sticky active">
 | 
			
		||||
				{#<a href="/"><img alt="logo" class="container-fluid" src="/static/images/dog_main_small.png"></a>#}
 | 
			
		||||
				<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.index') }}">{{ gettext('Account') }}</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.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.password_change') }}">{{ gettext('Password Change') }}</a></li>
 | 
			
		||||
				<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a></li>
 | 
			
		||||
			</div>
 | 
			
		||||
		</nav>
 | 
			
		||||
		{% endif %}
 | 
			
		||||
 | 
			
		||||
		<main class="col-md-9 ml-sm-auto col-lg-10 px-4" role="main">
 | 
			
		||||
			<h1>{% block title %}{% endblock %}</h1>
 | 
			
		||||
| 
						 | 
				
			
			@ -98,13 +44,6 @@
 | 
			
		|||
		</main>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class='container'>
 | 
			
		||||
	<div class="mt-5 row justify-content-center">
 | 
			
		||||
		<footer>
 | 
			
		||||
			<span class="text-muted">Render Time: {{ g.request_time() }}</span> | <span class="text-muted">{{ gettext('All right reserved. ©') + '2020' }}</span>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								templates/frontend/oauth2_tokens.html.j2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								templates/frontend/oauth2_tokens.html.j2
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
{% extends 'frontend/base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ gettext('Oauth2 tokens') }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<table class="table">
 | 
			
		||||
	<thead>
 | 
			
		||||
		<th>{{ gettext('Client ID') }}</th>
 | 
			
		||||
		<th>{{ gettext('Remember me') }}</th>
 | 
			
		||||
		<th>{{ gettext('Created at') }}
 | 
			
		||||
		<th>{{ gettext('Action') }}
 | 
			
		||||
	</thead>
 | 
			
		||||
	{% for consent_session in consent_sessions %}
 | 
			
		||||
	<tr>
 | 
			
		||||
		<td>{{ consent_session.consent_request.client.client_id }}</td>
 | 
			
		||||
		<td>{{ consent_session.remember }}</td>
 | 
			
		||||
		<td>{{ consent_session.handled_at }}</td>
 | 
			
		||||
		<td>
 | 
			
		||||
		<a title="{{ gettext('Revoke')}}" href="{{ url_for('.oauth2_token_revoke', client_id=consent_session.consent_request.client.client_id) }}" onclick="oauth2_token.revoke(this.href, '{{ consent_session.consent_request.client.client_id }}'); return false;"><i class="fas fa-ban"></i></a>
 | 
			
		||||
		</td>
 | 
			
		||||
	</tr>
 | 
			
		||||
	{% endfor %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										9
									
								
								templates/frontend/password_change.html.j2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/frontend/password_change.html.j2
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
{% extends 'frontend/base.html.j2' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ gettext('Password Change') }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
{{ render_form(form)}}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,27 @@
 | 
			
		|||
    {% block head %}{% endblock %}
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<div class='messages-box'>
 | 
			
		||||
</div>
 | 
			
		||||
<template id='confirm-dialog-template'>
 | 
			
		||||
	<div class="modal-dialog">
 | 
			
		||||
		<div class="modal-content">
 | 
			
		||||
 | 
			
		||||
			<div class="modal-header">
 | 
			
		||||
				<h5 class="modal-title"></h5>
 | 
			
		||||
				<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-body">
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="modal-footer">
 | 
			
		||||
				<button type="button" class="btn btn-danger close" data-dismiss="modal">Cancel</button>
 | 
			
		||||
				<button type="button" class="btn btn-primary btn-ok process">Process</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
{% block body %}{% endblock %}
 | 
			
		||||
<script type="application/javascript" src="/static/main.js?v={{ GIT_HASH }}" ></script>
 | 
			
		||||
<script type="application/javascript" >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue