diff --git a/hsapi/hsapi/preauthkeys.py b/hsapi/hsapi/preauthkeys.py
index 117092f..01a4747 100644
--- a/hsapi/hsapi/preauthkeys.py
+++ b/hsapi/hsapi/preauthkeys.py
@@ -16,7 +16,7 @@ class v1PreAuthKeyResponse(BaseModel):
class v1ExpirePreAuthKeyRequest(BaseModel):
user: str = Field(alias="user", default=None)
- key: int = Field(alias="key", default=None)
+ key: str = Field(alias="key", default=None)
class v1CreatePreAuthKeyRequest(BaseModel):
diff --git a/hsapi/hsapi/schemas.py b/hsapi/hsapi/schemas.py
index 025980b..3b309a7 100644
--- a/hsapi/hsapi/schemas.py
+++ b/hsapi/hsapi/schemas.py
@@ -45,7 +45,10 @@ class v1PreAuthKey(BaseModel):
def expired(self) -> bool:
tzinfo = timezone(timedelta(hours=0)) # UTC
now = datetime.now(tzinfo)
- return self.expiration < now # type: ignore
+ exptime = self.expiration < now
+ expused = not self.reusable and self.used
+ expephemereal = self.ephemeral and self.used
+ return exptime or expused or expephemereal
class v1User(BaseModel):
diff --git a/hsman/app/__init__.py b/hsman/app/__init__.py
index d6523ac..2bc08a9 100644
--- a/hsman/app/__init__.py
+++ b/hsman/app/__init__.py
@@ -13,11 +13,11 @@ import os
mobility = Mobility()
client_metadata = ClientMetadata(
- client_id='client-id',
- client_secret='client-secret')
+ client_id=os.getenv('HSMAN_OIDC_CLIENT_ID'),
+ client_secret=os.getenv('HSMAN_OIDC_CLIENT_SECRET'))
-provider_config = ProviderConfiguration(issuer='oidc-issuer-url',
+provider_config = ProviderConfiguration(issuer=os.getenv('HSMAN_OIDC_URL'),
client_metadata=client_metadata,
auth_request_params={
'scope': ['openid',
diff --git a/hsman/app/static/main.css b/hsman/app/static/main.css
index 4404482..aacc4f6 100644
--- a/hsman/app/static/main.css
+++ b/hsman/app/static/main.css
@@ -99,3 +99,7 @@ a.route.False {
div.dt-container div.dt-scroll-body {
border-bottom: none !important;
}
+
+tr.pka-hide {
+ visibility: collapse;
+}
diff --git a/hsman/app/static/main.js b/hsman/app/static/main.js
index 97f7753..0079a52 100644
--- a/hsman/app/static/main.js
+++ b/hsman/app/static/main.js
@@ -1,6 +1,6 @@
function renameNode(nodeId) {
var newName = $("#newName").val();
- var url = "/node/" + nodeId + "/rename/" + newName;
+ var url = `/node/${nodeId}/rename/${newName}`;
$.ajax({
url: url,
xhrFields: {
@@ -13,9 +13,38 @@ function renameNode(nodeId) {
});
}
+function createPKA(username) {
+ console.log(username);
+ var url = `/user/${username}/pakcreate`;
+ console.log(url);
+ var ephemereal = $("#ephemereal").is(":checked");
+ var reusable = $("#reusable").is(":checked");
+ var expiration = $("#expiration").val();
+ console.log(expiration);
+ $.ajax({
+ url: url,
+ method: "POST",
+ dataType: "json",
+ contentType: "application/json; charset=utf-8",
+ xhrFields: {
+ withCredentials: true,
+ },
+ data: JSON.stringify({
+ ephemeral: ephemereal,
+ reusable: reusable,
+ expiration: expiration,
+ }),
+
+ success: function (data) {
+ $("#createPKA").modal("hide");
+ location.reload();
+ },
+ });
+}
+
function copyToClipboard(obj) {
var span = $(obj);
- var value = span.attr("data-original-title");
+ var value = span.attr("value");
var original = span.html();
try {
navigator.clipboard.writeText(value);
@@ -28,3 +57,12 @@ function copyToClipboard(obj) {
console.error(error);
}
}
+
+function toggleExpired(obj) {
+ var toggle = $(obj);
+ if (toggle.is(":checked")) {
+ $(".pka-expired").removeClass("pka-hide");
+ } else {
+ $(".pka-expired").addClass("pka-hide");
+ }
+}
diff --git a/hsman/app/templates/node.html b/hsman/app/templates/node.html
index bcc3c65..0d9a02f 100644
--- a/hsman/app/templates/node.html
+++ b/hsman/app/templates/node.html
@@ -1,5 +1,4 @@
{% extends "base.html" %}
-
{% block content %}
@@ -51,21 +50,21 @@
-
+
+
+addresses
{% for ip in node.ipAddresses %}
-
{% endfor %}
+
+
tags
-
+
announced
@@ -74,7 +73,7 @@
{% if node.validTags %}
{% for tag in node.validTags %}
-
+
{{ tag }}
{% endfor %}
@@ -83,7 +82,7 @@
{% endif %}
-
+
forced
@@ -100,12 +99,12 @@
-
+
keys
-
-
+
+
machineKey
@@ -113,8 +112,8 @@
-
-
+
+
nodeKey
@@ -122,43 +121,43 @@
-
-
+
+
discoKey
{{ node.discoKey }}
-
+
routes
{% if isExitNode %}
-Exit Node
+Exit Node
{% endif %}
{% if routes %}
-
+
prefix
-
+
enabled
-
{% for route in routes | sort(attribute='prefix') %}
-
+
{{ route.prefix }}
-
+
{{ route.enabled | fancyBool | safe }}
-
+
{{ route.isPrimary | fancyBool | safe }}
@@ -169,6 +168,7 @@
No routes announced
+{% endif %}
@@ -191,5 +191,4 @@
-{% endif %}
{% endblock %}
diff --git a/hsman/app/templates/user.html b/hsman/app/templates/user.html
index 4719be4..ea770a1 100644
--- a/hsman/app/templates/user.html
+++ b/hsman/app/templates/user.html
@@ -14,12 +14,15 @@
registered
-
+
{{ user.createdAt | htime_dt }}
+
nodes
@@ -38,7 +41,9 @@
-
+
{{node.lastSeen | htime_dt }}
|
@@ -50,26 +55,40 @@
-
pre auth keys
+
+
+ pre auth keys
+
+
+
+
{% if preauthKeys %}
- |
+
+
+
+
+
+ |
created |
expiration |
attributes |
-
+
{% for key in preauthKeys %}
-
+
{{ key.key }}
+ value="{{ key.key}}"
+ title="click to copy full value"
+ class="pak_copy">{{ key.key[:5] }}…{{ key.key[-5:] }}
|
+
+
+
+
+
+ | -->
{% endfor %}
@@ -111,7 +134,38 @@
{% endif %}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% endblock %}
{% block scripts %}
@@ -121,6 +175,9 @@
$('.pak_copy').on('click', function() {
copyToClipboard(this)
})
+ $('#showExpired').on('change', function() {
+ toggleExpired(this)
+ })
new DataTable('#nodes', {
scrollY: 130,
@@ -138,7 +195,7 @@
keys: false,
});
new DataTable('#paks', {
- scrollY: 130,
+ scrollY: 230,
scrollCollapse: true,
paging: false,
// lengthMenu: [5, 10, 30, 50, { label: 'All', value: -1 }],
diff --git a/hsman/app/views.py b/hsman/app/views.py
index d042f12..c503523 100644
--- a/hsman/app/views.py
+++ b/hsman/app/views.py
@@ -1,22 +1,26 @@
-from flask import render_template, Blueprint
+import logging
+from .lib import remote_ip
+import datetime
+from flask import render_template, Blueprint, request
from flask import redirect, session, url_for
from app import auth
from flask import jsonify
from flask_pyoidc.user_session import UserSession
-from hsapi import Node, User, Route, PreAuthKey, v1ListPreAuthKeyRequest
+from hsapi import Node, User, Route, PreAuthKey
+from hsapi.preauthkeys import (v1ListPreAuthKeyRequest,
+ v1CreatePreAuthKeyRequest,
+ v1ExpirePreAuthKeyRequest)
-from .lib import remote_ip
-import logging
log = logging.getLogger()
main_blueprint = Blueprint('main', __name__)
-@main_blueprint.route('/', methods=['GET', 'POST'])
-@auth.access_control('default')
+@ main_blueprint.route('/', methods=['GET', 'POST'])
+@ auth.access_control('default')
def index():
user_session = UserSession(session)
hs_user = user_session.userinfo['email'].split('@')[0]
@@ -26,35 +30,35 @@ def index():
session=user_session)
-@main_blueprint.route('/token', methods=['GET', 'POST'])
-@auth.access_control('default')
+@ main_blueprint.route('/token', methods=['GET', 'POST'])
+@ auth.access_control('default')
def token():
user_session = UserSession(session)
return jsonify(user_session.userinfo)
-@main_blueprint.route('/call', methods=['GET', 'POST'])
-@auth.access_control('default')
+@ main_blueprint.route('/call', methods=['GET', 'POST'])
+@ auth.access_control('default')
def call():
return "CALL OK"
-@main_blueprint.route('/logout')
-@auth.oidc_logout
+@ main_blueprint.route('/logout')
+@ auth.oidc_logout
def logout():
return redirect('/')
-@main_blueprint.route('/nodes', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/nodes', methods=['GET'])
+@ auth.authorize_admins('default')
def nodes():
nodelist = Node().list()
return render_template('nodes.html',
nodes=nodelist.nodes)
-@main_blueprint.route('/node/', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/node/', methods=['GET'])
+@ auth.authorize_admins('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
@@ -69,8 +73,8 @@ def node(nodeId):
node=node)
-@main_blueprint.route('/users', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/users', methods=['GET'])
+@ auth.authorize_admins('default')
def users():
userList = User().list()
# Get online status of devices of the user
@@ -85,24 +89,27 @@ def users():
online=online)
-@main_blueprint.route('/user/', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/user/', methods=['GET'])
+@ auth.authorize_admins('default')
def user(userName):
user = User().get(userName)
userNodeList = [n for n in Node().list().nodes if n.user.name == userName]
- preauthkeyreq = v1ListPreAuthKeyRequest(user=userName)
+ preauthkeyreq = v1ListPreAuthKeyRequest(user=userName)
preauthKeys = PreAuthKey().list(preauthkeyreq)
- validpak = [k for k in preauthKeys.preAuthKeys if not k.expired]
+
+ defaultExpiry = datetime.datetime.now() + datetime.timedelta(days=7)
+ expStr = defaultExpiry.strftime('%Y-%m-%dT%H:%M')
return render_template("user.html",
user=user.user,
- preauthKeys=validpak,
+ defaultExpiry=expStr,
+ preauthKeys=preauthKeys.preAuthKeys,
userNodeList=userNodeList)
-@main_blueprint.route('/routes', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/routes', methods=['GET'])
+@ auth.authorize_admins('default')
def routes():
routes = Route().list()
@@ -120,8 +127,8 @@ def routes():
routes=final)
-@main_blueprint.route('/routeToggle/', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/routeToggle/', methods=['GET'])
+@ auth.authorize_admins('default')
def routeToggle(routeId: int):
routes = Route().list()
route = [r for r in routes.routes if r.id == routeId]
@@ -134,36 +141,36 @@ def routeToggle(routeId: int):
return redirect(url_for("main.routes"))
-@main_blueprint.route('/node//expire', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/node//expire', methods=['GET'])
+@ auth.authorize_admins('default')
def expireNode(nodeId: int):
Node().expire(nodeId)
return redirect(url_for("main.node", nodeId=nodeId))
-@main_blueprint.route('/node//list-expire', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/node//list-expire', methods=['GET'])
+@ auth.authorize_admins('default')
def expireNodeList(nodeId: int):
Node().expire(nodeId)
return redirect(url_for("main.nodes"))
-@main_blueprint.route('/node//delete', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/node//delete', methods=['GET'])
+@ auth.authorize_admins('default')
def deleteNode(nodeId: int):
Node().delete(nodeId)
return redirect(url_for("main.nodes"))
-@main_blueprint.route('/node//rename/', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/node//rename/', methods=['GET'])
+@ auth.authorize_admins('default')
def renameNode(nodeId: int, newName: str):
Node().rename(nodeId, newName)
return jsonify(dict(newName=newName))
-@main_blueprint.route('/user//delete', methods=['GET'])
-@auth.authorize_admins('default')
+@ main_blueprint.route('/user//delete', methods=['GET'])
+@ auth.authorize_admins('default')
def deleteUser(userName: str):
nodes = Node().byUser(userName)
for node in nodes.nodes:
@@ -171,3 +178,27 @@ def deleteUser(userName: str):
Node().delete(node.id)
User().delete(userName)
return redirect(url_for("main.users"))
+
+
+@ main_blueprint.route('/user//pakcreate', methods=['POST'])
+@ auth.authorize_admins('default')
+def createPKA(userName: str):
+ data = request.json
+ log.debug(data)
+ expiration = f"{data['expiration']}:00Z"
+ req = v1CreatePreAuthKeyRequest(user=userName,
+ reusable=data['reusable'],
+ ephemeral=data['ephemeral'],
+ expiration=expiration)
+ pak = PreAuthKey().create((req))
+ return jsonify(dict(key=pak.preAuthKey.key))
+
+
+@ main_blueprint.route('/user//expire/', 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))