Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
bdba6db42d
|
|||
|
4fb45c41bd
|
|||
|
a1c66152ae
|
|||
|
2a38fb14dd
|
|||
|
d86b2b58c2
|
|||
|
b91e73f3a5
|
|||
|
71a3413cbe
|
|||
|
a1dadcd709
|
|||
|
b9fd722016
|
|||
|
c780b12b74
|
|||
|
2719ecadf0
|
|||
|
fe9b42e8a2
|
|||
| 14b09a0fcf | |||
|
c39c3a0ab6
|
|||
|
07ac2edb53
|
@@ -17,10 +17,10 @@ You can run the Flask application as any other Flask app, using `flask run` insi
|
||||
There are some settings that must/can be provided to the application:
|
||||
|
||||
| Variable | Usage | Default |
|
||||
| -------------------------- | ---------------------------------------- | :-----: |
|
||||
| -------------------------- | -------------------------------------------------------------- | :-----: |
|
||||
| `APPLICATION_ROOT` | Base URI path for the app | `/` |
|
||||
| `HSMAN_SECRET_KEY` | Flask app secret key | |
|
||||
| `HSMAN_ADMIN_GROUPS` | User groups that are considered admins | |
|
||||
| `HSMAN_ADMIN_GROUPS` | Comma separated list of user groups that are considered admins | |
|
||||
| `HSMAN_OIDC_CLIENT_ID` | OIDC client ID | |
|
||||
| `HSMAN_OIDC_CLIENT_SECRET` | OIDC clietn secret | |
|
||||
| `HSMAN_OIDC_URL` | OIDC server URL | |
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from flask import Flask, render_template
|
||||
from flask import Flask, render_template, g
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from flask_mobility import Mobility
|
||||
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
|
||||
|
||||
|
||||
from . import filters
|
||||
from .lib import OIDCAuthentication
|
||||
@@ -29,6 +27,9 @@ def create_app(environment='development'):
|
||||
app.config.from_prefixed_env(prefix="HSMAN")
|
||||
config[env].configure(app)
|
||||
app.config['APP_TZ'] = os.environ.get('TZ', 'UTC')
|
||||
app.config['ADMIN_GROUPS'] = list(
|
||||
map(str.strip, app.config.get('ADMIN_GROUPS', "").split(',')))
|
||||
app.logger.debug(f"admin groups: {app.config['ADMIN_GROUPS']}")
|
||||
|
||||
app.logger.info("middleware init: mobility")
|
||||
mobility.init_app(app)
|
||||
@@ -38,21 +39,24 @@ def create_app(environment='development'):
|
||||
|
||||
# Register blueprints.
|
||||
from .views import main_blueprint, rest_blueprint
|
||||
app.logger.info(f"registering main blueprint with prefix '{
|
||||
main_blueprint.url_prefix}'")
|
||||
app.logger.info(f"register blueprint: 'main' [prefix '{
|
||||
main_blueprint.url_prefix}']")
|
||||
app.register_blueprint(main_blueprint)
|
||||
|
||||
app.logger.info(f"registering rest blueprint with prefix '{
|
||||
rest_blueprint.url_prefix}'")
|
||||
app.logger.info(f"register blueprint: 'rest' [prefix '{
|
||||
rest_blueprint.url_prefix}']")
|
||||
app.register_blueprint(rest_blueprint)
|
||||
|
||||
app.logger.info("jinja2 custom filters loaded")
|
||||
filters.init_app(app)
|
||||
|
||||
# Error handlers.
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_http_error(exc):
|
||||
return render_template('error.html', error=exc), exc.code
|
||||
|
||||
@app.context_processor
|
||||
def inject_auth():
|
||||
return dict(auth=auth)
|
||||
|
||||
return app
|
||||
|
||||
97
app/lib.py
97
app/lib.py
@@ -2,7 +2,7 @@ import os
|
||||
import functools
|
||||
|
||||
from flask import request, abort, current_app
|
||||
from flask import session as flask_session
|
||||
from flask import session as flask_session, jsonify
|
||||
from flask_pyoidc import OIDCAuthentication as _OIDCAuth
|
||||
from flask_pyoidc.user_session import UserSession
|
||||
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
|
||||
@@ -21,19 +21,6 @@ def remote_ip() -> str:
|
||||
return str(request.environ.get('REMOTE_ADDR'))
|
||||
|
||||
|
||||
def username() -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo['email'].split('@')[0]
|
||||
|
||||
|
||||
def login_name() -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
if 'preferred_username' in userinfo:
|
||||
return userinfo['preferred_username']
|
||||
else:
|
||||
return username()
|
||||
|
||||
|
||||
def webMode() -> bool:
|
||||
is_gunicorn = "gunicorn" in os.environ.get('SERVER_SOFTWARE', '')
|
||||
is_werkzeug = os.environ.get('WERKZEUG_RUN_MAIN', False) == "true"
|
||||
@@ -64,6 +51,68 @@ class OIDCAuthentication(_OIDCAuth):
|
||||
super().init_app(app)
|
||||
app.auth = self
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo['email'].split('@')[0]
|
||||
|
||||
@property
|
||||
def email(self) -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo['email']
|
||||
|
||||
@property
|
||||
def login_name(self) -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo.get('preferred_username', self.username)
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo.get('name')
|
||||
|
||||
@property
|
||||
def groups(self) -> list:
|
||||
userinfo = flask_session['userinfo']
|
||||
return userinfo.get('groups')
|
||||
|
||||
@property
|
||||
def isAdmin(self) -> bool:
|
||||
userinfo = flask_session['userinfo']
|
||||
user_groups = userinfo.get('groups', [])
|
||||
with current_app.app_context():
|
||||
admin_groups = current_app.config.get('ADMIN_GROUPS', [])
|
||||
admin_users = current_app.config.get('ADMIN_USERS', [])
|
||||
|
||||
authorized_groups = set(admin_groups).intersection(user_groups)
|
||||
|
||||
if len(authorized_groups):
|
||||
log.debug(f"'{self.username}' is a member of {
|
||||
authorized_groups}. isAdmin == True")
|
||||
return True
|
||||
|
||||
if self.username in admin_users:
|
||||
log.debug(f"'{self.username}' is an admin user")
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def unathorized(self):
|
||||
response = jsonify(
|
||||
{'message': f"not authorized",
|
||||
'comment': 'nice try, info logged',
|
||||
'logged': f"'{self.username}@{remote_ip()}",
|
||||
'result': 'GO AWAY!'})
|
||||
log.warning(
|
||||
f"user '{self.username}' attempted denied operation from {remote_ip()}")
|
||||
return response, 403
|
||||
|
||||
def userOrAdmin(self, username: str):
|
||||
"""
|
||||
Check is the current user is an admin OR the username passed as argument
|
||||
"""
|
||||
return self.isAdmin or self.username == username
|
||||
|
||||
def authorize(self, provider_name: str, authz_fn: Callable, **kwargs):
|
||||
if provider_name not in self._provider_configurations:
|
||||
raise ValueError(
|
||||
@@ -76,7 +125,7 @@ class OIDCAuthentication(_OIDCAuth):
|
||||
|
||||
# Decorator
|
||||
def oidc_decorator(view_func):
|
||||
@ functools.wraps(view_func)
|
||||
@functools.wraps(view_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
# Retrieve session and client
|
||||
session = UserSession(flask_session, provider_name)
|
||||
@@ -165,23 +214,7 @@ class OIDCAuthentication(_OIDCAuth):
|
||||
"""
|
||||
|
||||
def _authz_fn(session) -> bool:
|
||||
user_groups = session.userinfo.get('groups', [])
|
||||
username = session.userinfo.get('preferred_username', "")
|
||||
with current_app.app_context():
|
||||
admin_groups = current_app.config.get('ADMIN_GROUPS', [])
|
||||
admin_users = current_app.config.get('ADMIN_USERS', [])
|
||||
|
||||
authorized_groups = set(admin_groups).intersection(user_groups)
|
||||
|
||||
if len(authorized_groups):
|
||||
log.debug(f"'{username}' is a member of {
|
||||
authorized_groups}")
|
||||
return True
|
||||
|
||||
if username in admin_users:
|
||||
log.debug(f"'{username}' is an admin user")
|
||||
return True
|
||||
return False
|
||||
return self.isAdmin
|
||||
|
||||
return self.authorize(provider_name,
|
||||
authz_fn=_authz_fn)
|
||||
|
||||
@@ -14,12 +14,10 @@ function renameNode(nodeId) {
|
||||
}
|
||||
|
||||
function createPKA(username) {
|
||||
console.log(username);
|
||||
var url = `${username}/pakcreate`;
|
||||
var ephemereal = $("#ephemereal").is(":checked");
|
||||
var reusable = $("#reusable").is(":checked");
|
||||
var expiration = $("#expiration").val();
|
||||
console.log(expiration);
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "POST",
|
||||
@@ -65,3 +63,29 @@ function toggleExpired(obj) {
|
||||
$(".pka-expired").addClass("pka-hide");
|
||||
}
|
||||
}
|
||||
|
||||
function backfillips(obj) {
|
||||
var url = "backfillips";
|
||||
var button = $(obj);
|
||||
var original = button.html();
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
xhrFields: {
|
||||
withCredentials: true,
|
||||
},
|
||||
data: {},
|
||||
success: function (data) {
|
||||
if (data.length) {
|
||||
button.html("Updated");
|
||||
} else {
|
||||
button.html("Done");
|
||||
}
|
||||
setTimeout(function () {
|
||||
button.html(original);
|
||||
}, 500);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% if auth.isAdmin %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('main.nodes') }}">nodes</a>
|
||||
</li>
|
||||
@@ -59,6 +60,7 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('main.routes') }}">routes</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item me-right">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron my-4">
|
||||
<div class="jumbotron jumbotron-fluid my-4">
|
||||
<div class="text-center">
|
||||
<h1>{{ '%s - %s' % (error.code, error.name) }}</h1>
|
||||
<h1>Oops, something went wrong</h1>
|
||||
<h1>{{ '%s - %s' % (error.code, error.name) }}</h2>
|
||||
<p>{{ error.description }}.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h3>Welcome, {{ session.userinfo.name }}</h3>
|
||||
<h3>
|
||||
Welcome, {{ auth.full_name }}
|
||||
</h3>
|
||||
<hr>
|
||||
<h4>authentication info</h4>
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
<strong>email</strong>
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
{{ session.userinfo.email }}
|
||||
<!-- {{ session.userinfo.email_verified | fancyBool | safe }} -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
<strong>username</strong>
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
{{ session.userinfo.preferred_username }}
|
||||
<span data-toggle="tooltip" data-placement="right" title="OIDC username: {{ auth.login_name }}">
|
||||
{{ auth.username }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
<strong>email</strong>
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
{{ auth.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row data">
|
||||
@@ -27,19 +30,44 @@
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
<i class="fas fa-angle-right"></i>
|
||||
{{ session.userinfo.groups[0]}}
|
||||
{% if auth.groups[0] in config['ADMIN_GROUPS'] %}
|
||||
<span class="badge badge-pill badge-warning">
|
||||
{% else %}
|
||||
<span class="badge badge-pill badge-dark">
|
||||
{% endif %}
|
||||
{{ auth.groups[0]}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% for group in session.userinfo.groups[1:] |sort %}
|
||||
{% for group in auth.groups[1:] |sort %}
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
<i class="fas fa-angle-right"></i> {{ group }}
|
||||
<i class="fas fa-angle-right"></i>
|
||||
{% if group in config['ADMIN_GROUPS'] %}
|
||||
<span class="badge badge-pill badge-warning">
|
||||
{% else %}
|
||||
<span class="badge badge-pill badge-dark">
|
||||
{% endif %}
|
||||
{{ group }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
<strong>access level</strong>
|
||||
</div>
|
||||
<div class="col col-6">
|
||||
{% if auth.isAdmin %}
|
||||
<span class="badge badge-pill badge-danger">ADMIN</span>
|
||||
{% else %}
|
||||
<span class="badge badge-pill badge-info">USER</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h4>your devices</h4>
|
||||
<div class="row strong">
|
||||
@@ -52,7 +80,7 @@
|
||||
{% for node in userNodeList %}
|
||||
<div class="row data">
|
||||
<div class="col col-2">
|
||||
{{ node.givenName}}
|
||||
<a href="{{url_for('main.node', nodeId=node.id) }}">{{ node.givenName}}</a>
|
||||
</div>
|
||||
<div class="col col-2">
|
||||
<span data-toggle="tooltip" data-placement="right" title="{{ node.createdAt | fmt_datetime }}">
|
||||
@@ -69,7 +97,7 @@
|
||||
</div>
|
||||
<div class="col col-2">
|
||||
<span data-toggle="tooltip" data-placement="right" title="delete">
|
||||
<a class="nodeco" href="{{ url_for('rest.deleteOwnNode', nodeId=node.id) }}">
|
||||
<a class="nodeco" href="{{ url_for('rest.deleteNode', nodeId=node.id) }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<div class="col col-8 float-left">
|
||||
<span data-toggle="tooltip" data-placement="right" title="{{ node.createdAt | fmt_datetime }}">
|
||||
{{ node.createdAt | htime_dt }}
|
||||
<span class="badge badge-pill badge-warning">
|
||||
{{ node.registerMethod.name }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +77,10 @@
|
||||
{% for ip in node.ipAddresses %}
|
||||
<div class="row data">
|
||||
<div class="col col-3">
|
||||
<span class="address copy"
|
||||
value="{{ ip }}">
|
||||
{{ ip }}
|
||||
</spanundefined>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -210,3 +216,13 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(function () {
|
||||
$('.address.copy').on('click', function() {
|
||||
copyToClipboard(this)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h3>nodes</h3>
|
||||
<div class="row data justify-content-between">
|
||||
<div class="col col-4">
|
||||
<h3>nodes</h3>
|
||||
</div>
|
||||
<div class="col col-2">
|
||||
<span data-toggle="tooltip" data-placement="right" title="Recheck all IP addresses of all nodes">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onClick="backfillips(this);">Backfill IP addresses</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<p></p>
|
||||
<table id="nodes" class="display" style="width:100%">
|
||||
@@ -52,7 +61,7 @@
|
||||
<td class="no-sort">
|
||||
{% if node.expireDate and not node.expired %}
|
||||
<span data-toggle="tooltip" data-placement="right" title="expire/disconnect">
|
||||
<a class="nodeco" href="{{ url_for('rest.expireNodeList', nodeId=node.id) }}">
|
||||
<a class="nodeco" href="{{ url_for('rest.expireNode', nodeId=node.id) }}">
|
||||
<i class="fas fa-plug"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<td class="no-sort">
|
||||
{% if node.expireDate and not node.expired %}
|
||||
<span data-toggle="tooltip" data-placement="right" title="expire/disconnect">
|
||||
<a class="nodeco" href="{{ url_for('rest.expireNodeUser', nodeId=node.id) }}">
|
||||
<a class="nodeco" href="{{ url_for('rest.expireNode', nodeId=node.id) }}">
|
||||
<i class="fas fa-plug"></i>
|
||||
</a>
|
||||
</span>
|
||||
@@ -57,7 +57,7 @@
|
||||
<i class="fas fa-plug disabled"></i>
|
||||
{% endif %}
|
||||
<span data-toggle="tooltip" data-placement="right" title="delete">
|
||||
<a class="nodeco" href="{{ url_for('rest.deleteNodeUser', nodeId=node.id) }}">
|
||||
<a class="nodeco" href="{{ url_for('rest.deleteNode', nodeId=node.id) }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class="plain" href="{{ url_for('main.user', userName=user.name)}}">
|
||||
<a class="plain" href="{{ url_for('main.user', userName=user.name) }}">
|
||||
{{user.name}}
|
||||
</a>
|
||||
</td>
|
||||
@@ -31,7 +31,7 @@
|
||||
</td>
|
||||
<td class="no-sort">
|
||||
<span data-toggle="tooltip" data-placement="right" title="delete">
|
||||
<a class="nodeco" href="/user/{{user.name}}/delete">
|
||||
<a class="nodeco" href="{{ url_for('rest.deleteUser', userName=user.name) }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
import datetime
|
||||
import os
|
||||
from flask import current_app
|
||||
from flask import render_template, Blueprint, request
|
||||
from flask import render_template, Blueprint
|
||||
from flask import redirect, session, url_for
|
||||
from app import auth
|
||||
|
||||
@@ -26,27 +26,24 @@ def health():
|
||||
return jsonify(dict(status="OK", version=current_app.config['APP_VERSION']))
|
||||
|
||||
|
||||
@main_blueprint.route('/', methods=['GET', 'POST'])
|
||||
@auth.access_control('default')
|
||||
def index():
|
||||
user_session = UserSession(session)
|
||||
hs_user = user_session.userinfo['email'].split('@')[0]
|
||||
userNodeList = [n for n in Node().list().nodes if n.user.name == hs_user]
|
||||
return render_template('index.html',
|
||||
userNodeList=userNodeList,
|
||||
session=user_session)
|
||||
|
||||
|
||||
@main_blueprint.route('/token', methods=['GET', 'POST'])
|
||||
@auth.authorize_admins('default')
|
||||
def token():
|
||||
user_session = UserSession(session)
|
||||
# return jsonify(user_session.userinfo)
|
||||
return jsonify(access_token=user_session.access_token,
|
||||
id_token=user_session.id_token,
|
||||
userinfo=user_session.userinfo)
|
||||
|
||||
|
||||
@main_blueprint.route('/', methods=['GET', 'POST'])
|
||||
@auth.access_control('default')
|
||||
def index():
|
||||
hs_user = auth.username
|
||||
userNodeList = [n for n in Node().list().nodes if n.user.name == hs_user]
|
||||
return render_template('index.html',
|
||||
userNodeList=userNodeList)
|
||||
|
||||
|
||||
@main_blueprint.route('/logout')
|
||||
@auth.oidc_logout
|
||||
def logout():
|
||||
@@ -62,12 +59,14 @@ def nodes():
|
||||
|
||||
|
||||
@main_blueprint.route('/node/<int:nodeId>', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
@auth.access_control('default')
|
||||
def node(nodeId):
|
||||
# There is a bug in HS api with retrieving a single node
|
||||
# and we added a workaround to hsapi, so node.get() returns a
|
||||
# v1Node object instead of v1NodeResponse, so we access directly
|
||||
# `node`, instead of `node.node`
|
||||
if not auth.userOrAdmin(auth.username):
|
||||
return auth.unathorized
|
||||
node = Node().get(nodeId)
|
||||
routes = Node().routes(nodeId)
|
||||
isExitNode = any(
|
||||
|
||||
@@ -4,18 +4,17 @@ from flask import Blueprint, request
|
||||
from flask import redirect, url_for
|
||||
from app import auth
|
||||
|
||||
from ..lib import login_name, username
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
from hsapi_client import Node, User, Route, PreAuthKey
|
||||
from hsapi_client.preauthkeys import (v1CreatePreAuthKeyRequest,
|
||||
v1ExpirePreAuthKeyRequest)
|
||||
from hsapi_client.nodes import v1BackfillNodeIPsResponse
|
||||
|
||||
|
||||
log = logging.getLogger()
|
||||
# REST calls
|
||||
|
||||
# REST calls
|
||||
rest_blueprint = Blueprint(
|
||||
'rest', __name__, url_prefix=os.getenv('APPLICATION_ROOT', '/'))
|
||||
|
||||
@@ -23,98 +22,58 @@ rest_blueprint = Blueprint(
|
||||
@rest_blueprint.route('/routeToggle/<int:routeId>', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
def routeToggle(routeId: int):
|
||||
routes = Route().list()
|
||||
route = [r for r in routes.routes if r.id == routeId]
|
||||
route = Route().get(routeId)
|
||||
if route:
|
||||
route = route[0]
|
||||
if route.enabled:
|
||||
action = 'disabled'
|
||||
Route().disable(routeId)
|
||||
else:
|
||||
Route().enable(routeId)
|
||||
action = 'enabled'
|
||||
log.info(
|
||||
f"route '{route.prefix}' via '{route.node.givenName}'"
|
||||
f"{action} by '{username()}'")
|
||||
return redirect(url_for("main.routes"))
|
||||
f"route '{route.prefix}' via '{route.node.givenName}' "
|
||||
f"{action} by '{auth.username}'")
|
||||
Route().toggle(routeId)
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@rest_blueprint.route('/node/<int:nodeId>/expire', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
@auth.access_control('default')
|
||||
def expireNode(nodeId: int):
|
||||
"""
|
||||
This expires a node from the node page.
|
||||
The difference from above is that it returns to the /node/nodeId page
|
||||
"""
|
||||
Node().expire(nodeId)
|
||||
log.info(f"node '{nodeId}' expired by '{username()}'")
|
||||
return redirect(url_for("main.node", nodeId=nodeId))
|
||||
|
||||
|
||||
@rest_blueprint.route('/node/<int:nodeId>/user-expire', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
def expireNodeUser(nodeId: int):
|
||||
"""
|
||||
This expires a node from the node page.
|
||||
The difference from above is that it returns to the /node/nodeId page
|
||||
"""
|
||||
node = Node().get(nodeId)
|
||||
userName = node.user.name
|
||||
if not auth.userOrAdmin(node.user.name):
|
||||
return auth.unathorized
|
||||
Node().expire(nodeId)
|
||||
log.info(f"node '{nodeId}' expired by '{username()}'")
|
||||
return redirect(url_for("main.user", userName=userName))
|
||||
log.info(f"node '{nodeId}' expired by '{auth.username}'")
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@rest_blueprint.route('/node/<int:nodeId>/list-expire', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
def expireNodeList(nodeId: int):
|
||||
"""
|
||||
This expires a node from the node list.
|
||||
The difference from above is that it returns to the /nodes page
|
||||
"""
|
||||
Node().expire(nodeId)
|
||||
log.info(f"node '{nodeId}' expired by '{username()}'")
|
||||
return redirect(url_for("main.nodes"))
|
||||
|
||||
|
||||
@ rest_blueprint.route('/node/<int:nodeId>/delete', methods=['GET'])
|
||||
@ auth.authorize_admins('default')
|
||||
@rest_blueprint.route('/node/<int:nodeId>/delete', methods=['GET'])
|
||||
@auth.access_control('default')
|
||||
def deleteNode(nodeId: int):
|
||||
Node().delete(nodeId)
|
||||
log.info(f"node '{nodeId}' deleted by '{username()}'")
|
||||
return redirect(url_for("main.nodes"))
|
||||
|
||||
|
||||
@rest_blueprint.route('/node/<int:nodeId>/delete-own', methods=['GET'])
|
||||
@auth.access_control('default')
|
||||
def deleteOwnNode(nodeId: int):
|
||||
node = Node().get(nodeId)
|
||||
if node.user.name != username():
|
||||
response = jsonify({'message': 'not authorized'})
|
||||
return response, 401
|
||||
if not auth.userOrAdmin(node.user.name):
|
||||
return auth.unathorized
|
||||
Node().expire(nodeId)
|
||||
Node().delete(nodeId)
|
||||
log.info(f"'{username()}' delete their own node '{nodeId}'")
|
||||
return redirect(url_for("main.index"))
|
||||
log.info(f"node '{nodeId}' deleted by '{auth.username}'")
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@rest_blueprint.route('/node/<int:nodeId>/delete-user', methods=['GET'])
|
||||
@rest_blueprint.route('/node/<int:nodeId>/rename/<newName>', methods=['GET'])
|
||||
@auth.access_control('default')
|
||||
def deleteNodeUser(nodeId: int):
|
||||
node = Node().get(nodeId)
|
||||
Node().delete(nodeId)
|
||||
log.info(f"'{username()}' delete their own node '{nodeId}'")
|
||||
return redirect(url_for("main.user", userName=node.user.name))
|
||||
|
||||
|
||||
@ rest_blueprint.route('/node/<int:nodeId>/rename/<newName>', methods=['GET'])
|
||||
@ auth.authorize_admins('default')
|
||||
def renameNode(nodeId: int, newName: str):
|
||||
node = Node().get(nodeId)
|
||||
if not auth.userOrAdmin(node.user.name):
|
||||
return auth.unathorized
|
||||
Node().rename(nodeId, newName)
|
||||
return jsonify(dict(newName=newName))
|
||||
|
||||
|
||||
@ rest_blueprint.route('/user/<userName>/delete', methods=['GET'])
|
||||
@ auth.authorize_admins('default')
|
||||
@rest_blueprint.route('/user/<userName>/delete', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
def deleteUser(userName: str):
|
||||
nodes = Node().byUser(userName)
|
||||
for node in nodes.nodes:
|
||||
@@ -124,8 +83,8 @@ def deleteUser(userName: str):
|
||||
return redirect(url_for("main.users"))
|
||||
|
||||
|
||||
@ rest_blueprint.route('/user/<userName>/pakcreate', methods=['POST'])
|
||||
@ auth.authorize_admins('default')
|
||||
@rest_blueprint.route('/user/<userName>/pakcreate', methods=['POST'])
|
||||
@auth.authorize_admins('default')
|
||||
def createPKA(userName: str):
|
||||
data = request.json
|
||||
log.debug(data)
|
||||
@@ -138,11 +97,18 @@ def createPKA(userName: str):
|
||||
return jsonify(dict(key=pak.preAuthKey.key))
|
||||
|
||||
|
||||
@ rest_blueprint.route('/user/<userName>/expire/<key>', methods=['GET'])
|
||||
@ auth.authorize_admins('default')
|
||||
@rest_blueprint.route('/user/<userName>/expire/<key>', methods=['GET'])
|
||||
@auth.authorize_admins('default')
|
||||
def expirePKA(userName: str, key: str):
|
||||
log.debug(key)
|
||||
req = v1ExpirePreAuthKeyRequest(user=userName, key=key)
|
||||
|
||||
PreAuthKey().expire(req)
|
||||
return redirect(url_for('main.user', userName=userName))
|
||||
|
||||
|
||||
@rest_blueprint.route('/backfillips', methods=['POST'])
|
||||
@auth.authorize_admins('default')
|
||||
def backfillips():
|
||||
response = Node().backfillips()
|
||||
return jsonify(response.changes)
|
||||
|
||||
@@ -17,7 +17,7 @@ class BaseConfig(object):
|
||||
# All the followinf vars can be overriden
|
||||
# in the environment, using `HSMAN_` prefix
|
||||
SECRET_KEY = "secreto"
|
||||
ADMIN_GROUPS = ["adminGroup"]
|
||||
ADMIN_GROUPS = "adminGroup"
|
||||
OIDC_CLIENT_ID = 'client-id'
|
||||
OIDC_CLIENT_SECRET = 'client-secreto'
|
||||
OIDC_URL = "https://myidp.example.com/auth"
|
||||
|
||||
@@ -11,7 +11,7 @@ preload_app = True
|
||||
|
||||
# logconfig = "app/logging/production.ini"
|
||||
logconfig = "app/logging/production.ini"
|
||||
# access_log_format = "%(h)s %(l)s %(t)s %(r)s %(s)s %(b)s %(f)s %(a)s"
|
||||
access_log_format = "%(h)s %({x-forwarded-for}i)s %(t)s %(r)s %(s)s %(b)s %(L)s"
|
||||
# Log to stdout.
|
||||
accesslog = "-"
|
||||
errorlog = "-"
|
||||
|
||||
87
poetry.lock
generated
87
poetry.lock
generated
@@ -241,43 +241,38 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "42.0.8"
|
||||
version = "43.0.0"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
|
||||
{file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
|
||||
{file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
|
||||
{file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
|
||||
{file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
|
||||
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
|
||||
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
|
||||
{file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
|
||||
{file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
|
||||
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
|
||||
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
|
||||
{file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
|
||||
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -290,7 +285,7 @@ nox = ["nox"]
|
||||
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||
sdist = ["build"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
@@ -457,13 +452,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "hsapi-client"
|
||||
version = "0.9.3"
|
||||
version = "0.9.7"
|
||||
description = "Headscale API client"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.11"
|
||||
files = [
|
||||
{file = "hsapi_client-0.9.3-py3-none-any.whl", hash = "sha256:75bf3e5f35a857c36f49560ba8d70243b3dc66138f50dbf2b862ae240a68b9ab"},
|
||||
{file = "hsapi_client-0.9.3.tar.gz", hash = "sha256:58e1494608e17b224d27ca7fa004219cc1a1f7926c677e16fc21774e9502ed25"},
|
||||
{file = "hsapi_client-0.9.7-py3-none-any.whl", hash = "sha256:6cd8ac2a787112a02d7d5d3e029ceba0749844806b20b3c27247393cccd53def"},
|
||||
{file = "hsapi_client-0.9.7.tar.gz", hash = "sha256:7a6bf7cb533a4f0431c322bc292f09559eb27b37177ea2101a6ea559dc0c9e47"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -473,13 +468,13 @@ requests = ">=2.32.3,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "humanize"
|
||||
version = "4.9.0"
|
||||
version = "4.10.0"
|
||||
description = "Python humanize utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"},
|
||||
{file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"},
|
||||
{file = "humanize-4.10.0-py3-none-any.whl", hash = "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6"},
|
||||
{file = "humanize-4.10.0.tar.gz", hash = "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -793,13 +788,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
description = "Safely evaluate AST nodes without side effects"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
|
||||
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
|
||||
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
|
||||
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1139,13 +1134,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.30.1"
|
||||
version = "0.30.3"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
|
||||
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
|
||||
{file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"},
|
||||
{file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "hsman"
|
||||
version = "0.9.6"
|
||||
version = "0.9.16"
|
||||
description = "Flask Admin webui for Headscale"
|
||||
authors = ["Andrea Mistrali <andrea@mistrali.pw>"]
|
||||
license = "BSD"
|
||||
|
||||
3
wsgi.py
3
wsgi.py
@@ -1,6 +1,5 @@
|
||||
from app import create_app
|
||||
from app import lib
|
||||
from app import models
|
||||
import logging
|
||||
import logging.config
|
||||
import os
|
||||
@@ -20,7 +19,7 @@ log.debug(f"Running in web mode: {lib.webMode()}")
|
||||
def get_context():
|
||||
# flask cli context setup
|
||||
"""Objects exposed here will be automatically available from the shell."""
|
||||
return dict(app=app, models=models)
|
||||
return dict(app=app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user