Skip to content

Commit

Permalink
Add frontend for JWT refresh tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
stveit committed Feb 6, 2025
1 parent 2889fe6 commit 38ae35e
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelog.d/3273.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add frontend for managing JWT refresh tokens
4 changes: 2 additions & 2 deletions python/nav/models/sql/changes/sc.05.13.0001.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CREATE TABLE manage.JWTRefreshToken (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL UNIQUE,
description VARCHAR,
expires TIMESTAMP NOT NULL,
activates TIMESTAMP NOT NULL,
expires TIMESTAMPTZ NOT NULL,
activates TIMESTAMPTZ NOT NULL,
hash VARCHAR NOT NULL
);
66 changes: 66 additions & 0 deletions python/nav/web/templates/useradmin/jwt_created.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{% extends "useradmin/base.html" %}

{% block base_header_additional_head %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/nav/multi-select.css">
<style>
#edit-token-form input[type=submit] { float:left; margin-right: 1.25rem }
label[for='id_endpoints'] { display: none; }
</style>
<script>
require(['libs/jquery.multi-select'], function(){
$(function(){
/* Add multiselect for selecting endpoints */
var msSelector = '#id_endpoints';
$(msSelector).multiSelect({
selectableHeader: 'Available endpoints <a id="select-all-endpoints" class="right">Select all</a>',
selectionHeader: 'Selected endpoints <a id="remove-all-endpoints" class="right">Remove all</a>',
afterInit: function(){
$('#select-all-endpoints').click(function(){ $(msSelector).multiSelect('select_all') });
$('#remove-all-endpoints').click(function(){ $(msSelector).multiSelect('deselect_all') });
}
});

/* This is the code for closing the content dropdown */
$('#confirm-token-delete .close-button').click(function () {
$(document).foundation('dropdown', 'close', $(this).parents('.f-dropdown:first'));
});
});
});
</script>

<style>
.token-card { max-width: 470px; overflow-wrap: break-word; }
.token-card .label { cursor: help; margin-bottom: 1px; }
.token-card code { display: inline-block; width: 100%; }
</style>
{% endblock %}


{% block content %}

Back to
<a href="{% url 'useradmin-jwt_list' %}">token list</a>

<div id="form-panel" class="token-card panel white">

<h3>
{{ object.name }}
</h3>

<code id="token" style="display:block; white-space:pre-wrap">{{ token }}</code>

<button onclick="myFunction()">Copy to clipboard</button>

<script>
function myFunction() {
var copyText = document.getElementById("token");
navigator.clipboard.writeText(copyText.innerHTML);
}
</script>


<div class="float-clear"></div>

</div>

{% endblock %}
53 changes: 53 additions & 0 deletions python/nav/web/templates/useradmin/jwt_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% extends "useradmin/base.html" %}
{% load info %}

{# Display information about a single token #}


{% block base_header_additional_head %}
<style>
.token-card { max-width: 470px; overflow-wrap: break-word;}
.token-card .label { cursor: help; margin-bottom: 1px; }
.token-card code { display: inline-block; width: 100%;}
</style>
{% endblock %}


{% block content %}

<a href="{% url 'useradmin-jwt_list' %}">Back to token list</a>

<div class="row">
<div class="column small-12">

<div class="token-card panel white">
<a href="{% url 'useradmin-jwt_edit' object.pk %}" class="right">Edit token</a>

<h3>Token details</h3>

<p>
<code>{{ object.name }}</code>
</p>

{% if object.description %}
<h4>Description</h4>
<div class="panel">
<p>
{{ object.description }}
</p>
</div>
{% endif %}
<h4>Active</h4>
<div class="panel">
<p>
{{ object.is_active }}
</p>
</div>

</div> {# end panel #}


</div> {# end column #}
</div> {# end row #}

{% endblock %}
87 changes: 87 additions & 0 deletions python/nav/web/templates/useradmin/jwt_edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{% extends "useradmin/base.html" %}

{% block base_header_additional_head %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/nav/multi-select.css">
<style>
#edit-token-form input[type=submit] { float:left; margin-right: 1.25rem }
label[for='id_endpoints'] { display: none; }
</style>
<script>
require(['libs/jquery.multi-select'], function(){
$(function(){
/* Add multiselect for selecting endpoints */
var msSelector = '#id_endpoints';
$(msSelector).multiSelect({
selectableHeader: 'Available endpoints <a id="select-all-endpoints" class="right">Select all</a>',
selectionHeader: 'Selected endpoints <a id="remove-all-endpoints" class="right">Remove all</a>',
afterInit: function(){
$('#select-all-endpoints').click(function(){ $(msSelector).multiSelect('select_all') });
$('#remove-all-endpoints').click(function(){ $(msSelector).multiSelect('deselect_all') });
}
});

/* This is the code for closing the content dropdown */
$('#confirm-token-delete .close-button').click(function () {
$(document).foundation('dropdown', 'close', $(this).parents('.f-dropdown:first'));
});
});
});
</script>
{% endblock %}


{% block content %}

Back to
<a href="{% url 'useradmin-jwt_list' %}">token list</a>
{% if object %}
| <a href="{% url 'useradmin-jwt_detail' object.pk %}">token details</a>
{% endif %}

<div id="form-panel" class="panel white">

<h4>
{% if object %}
Edit token
{% else %}
Create new token
{% endif %}
</h4>


{% include 'custom_crispy_templates/flat_form.html' %}


{% if object %}
{# Display button for expiring a token #}
<form action="{% url 'useradmin-jwt_expire' object.pk %}" method="post">
<input type="submit" value="Expire token" class="button small">
</form>

{# Display button for recreating a token #}
<form action="{% url 'useradmin-jwt_recreate' object.pk %}" method="post">
<input type="submit" value="Recreate token" class="button small">
</form>

{# Display form for deleting a token #}
<a href="javascript:void(0);" class="button small alert"
data-options="align:top"
data-dropdown="confirm-token-delete">
Delete token
</a>

<div id="confirm-token-delete" class="f-dropdown content">
<form id="delete-token-form"
action="{% url 'useradmin-jwt_delete' object.pk %}"
method="post">{% csrf_token %}
<input type="submit" value="Yes, delete" class="button small alert expand">
</form>
<span class="button secondary small close-button expand">No, don't delete</span>
</div>
{% endif %}

<div class="float-clear"></div>

</div>

{% endblock %}
110 changes: 110 additions & 0 deletions python/nav/web/templates/useradmin/jwt_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@

{% extends "useradmin/base.html" %}
{% load info %}


{% block base_header_additional_head %}
<style>
#token-list-table .label { cursor: help; margin-bottom: 1px; }
</style>
<script>
require(['libs/jquery.tablesorter.min'], function() {
$(function() {
var $table = $('#token-list-table');
if ($table.length && $table.find('tbody tr').length > 0) {
$table.tablesorter({
sortList: [[2, 0]],
headers: {
5: {sorter: false},
6: {sorter: false},
7: {sorter: false}
}
});
}
});
});
</script>
{% endblock %}


{% block content %}

<div class="tabs">
{% include 'useradmin/tabs.html' %}

<div class="tabcontent">
<a href="{% url 'useradmin-jwt_create' %}" class="button small">
Create new token
</a>

{# Show table with tokens if there are any #}
{% if object_list %}

<table id="token-list-table" class="listtable hover tablesorter">
<caption>
List of issued API tokens
</caption>

<thead>
<tr>
<th>Name</th>
<th>Activates at</th>
<th>Expires at</th>
<th>Status</th>
<th>&nbsp;</th>
</tr>
</thead>

<tbody>
{% for token in object_list %}
<tr>
{# Name #}
<td>
<a href="{% url 'useradmin-jwt_detail' token.pk %}" title="Details">{{ token.name }}</a>
</td>

{# Activates at #}
<td>
{{ token.activates }}
</td>

{# Expires at #}
<td>
{{ token.expires}}
</td>

{# Status #}
<td>
{% if not token.is_active%}
<span class="label warning" title="Token Inactive">
Inactive
</span>
{% else %}
<span class="label success" title="Token Active">
Active
</span>
{% endif %}
</td>

{# Link to token edit #}
<td>
<a href="{% url 'useradmin-jwt_edit' token.pk %}" title="Edit this token">Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

{% else %}

{# If there are no tokens, show information about creating one #}
<div class="alert-box info">No tokens are issued. Do you want to
<a href="{% url 'useradmin-jwt_create' %}">create one</a>?
</div>

{% endif %}

</div> {# .tabcontent #}
</div> {# .tabs #}

{% endblock %}
49 changes: 49 additions & 0 deletions python/nav/web/templates/useradmin/jwt_not_enabled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "useradmin/base.html" %}

{% block base_header_additional_head %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/nav/multi-select.css">
<style>
#edit-token-form input[type=submit] { float:left; margin-right: 1.25rem }
label[for='id_endpoints'] { display: none; }
</style>
<script>
require(['libs/jquery.multi-select'], function(){
$(function(){
/* Add multiselect for selecting endpoints */
var msSelector = '#id_endpoints';
$(msSelector).multiSelect({
selectableHeader: 'Available endpoints <a id="select-all-endpoints" class="right">Select all</a>',
selectionHeader: 'Selected endpoints <a id="remove-all-endpoints" class="right">Remove all</a>',
afterInit: function(){
$('#select-all-endpoints').click(function(){ $(msSelector).multiSelect('select_all') });
$('#remove-all-endpoints').click(function(){ $(msSelector).multiSelect('deselect_all') });
}
});

/* This is the code for closing the content dropdown */
$('#confirm-token-delete .close-button').click(function () {
$(document).foundation('dropdown', 'close', $(this).parents('.f-dropdown:first'));
});
});
});
</script>
{% endblock %}


{% block content %}

Back to
<a href="{% url 'useradmin-jwt_list' %}">token list</a>

<div id="form-panel" class="panel white">


<h4>
You must configure jwt.conf before using this feature.
</h4>

<div class="float-clear"></div>

</div>

{% endblock %}
3 changes: 3 additions & 0 deletions python/nav/web/templates/useradmin/tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@
<li{% if active.token_list %} class="tabactive"{% endif %}>
<a href="{% url 'useradmin-token_list' %}">API Token List</a>
</li>
<li{% if active.jwt_list %} class="tabactive"{% endif %}>
<a href="{% url 'useradmin-jwt_list' %}">JWT Token List</a>
</li>
</ul>
2 changes: 1 addition & 1 deletion python/nav/web/templates/useradmin/token_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

{% block base_header_additional_head %}
<style>
.token-card { max-width: 470px; }
.token-card { max-width: 470px; overflow-wrap: break-word; }
.token-card .label { cursor: help; margin-bottom: 1px; }
.token-card code { display: inline-block; width: 100%; }
</style>
Expand Down
Loading

0 comments on commit 38ae35e

Please sign in to comment.