Skip to content

Commit da852c9

Browse files
committed
implement the API key management views
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent fe1da20 commit da852c9

5 files changed

Lines changed: 73 additions & 15 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ dependencies = [
9696
"aboutcode.hashid==0.2.0",
9797
# AboutCode pipeline
9898
"aboutcode.pipeline==0.2.1",
99-
"aboutcode.api-auth==0.1.0",
99+
"aboutcode.api-auth==0.2.0",
100100
# ScoreCode
101101
"scorecode==0.0.4"
102102
]

scancodeio/urls.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from scanpipe.api.views import ProjectViewSet
3333
from scanpipe.api.views import RunViewSet
3434
from scanpipe.views import AccountProfileView
35+
from scanpipe.views import GenerateAPIKeyView
36+
from scanpipe.views import RevokeAPIKeyView
3537

3638
api_router = DefaultRouter()
3739
api_router.register(r"projects", ProjectViewSet)
@@ -45,6 +47,16 @@
4547
name="logout",
4648
),
4749
path("accounts/profile/", AccountProfileView.as_view(), name="account_profile"),
50+
path(
51+
"accounts/profile/api_key/generate/",
52+
GenerateAPIKeyView.as_view(),
53+
name="generate_api_key",
54+
),
55+
path(
56+
"accounts/profile/api_key/revoke/",
57+
RevokeAPIKeyView.as_view(),
58+
name="revoke_api_key",
59+
),
4860
]
4961

5062

scanpipe/templates/account/profile.html

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,48 @@
1414
</nav>
1515
</div>
1616

17-
<article class="message is-warning">
18-
<div class="message-body">
19-
<strong>An API key is like a password and should be treated with the same care.</strong>
20-
</div>
21-
</article>
22-
23-
<div class="field">
24-
<label class="label">API Key</label>
25-
<div class="control has-icons-left">
26-
<input class="input" type="text" value="{{ request.user.auth_token.key|default:'Not available' }}" readonly>
27-
<span class="icon is-small is-left">
28-
<i class="fa-solid fa-key"></i>
29-
</span>
17+
<div class="columns">
18+
<div class="column is-7">
19+
<div class="content">
20+
<p>
21+
Your personal API key provides access to the <a href="{% url 'project-list' %}" target="_blank">REST API</a>.<br>
22+
<strong>Treat it like a password and keep it secure.</strong>
23+
</p>
24+
{% if request.user.api_token %}
25+
<div class="notification is-info is-light mb-4">
26+
Your API key <strong>{{ request.user.api_token.prefix }}...</strong>
27+
was generated on {{ request.user.api_token.created }}<br>
28+
For security reasons, the full key is only shown once at generation time.<br>
29+
If you lose it, you will need to regenerate a new one.
30+
</div>
31+
{% else %}
32+
<div class="notification is-warning is-light mb-4">
33+
<strong>No API key created.</strong><br>
34+
Generate one using the button below to access the REST API.
35+
</div>
36+
{% endif %}
37+
</div>
38+
<div class="buttons">
39+
<button type="button" class="button is-link is-outlined modal-button">
40+
Generate API key
41+
</button>
42+
{% if request.user.api_token %}
43+
<button type="button" class="button is-danger is-outlined modal-button">
44+
Revoke API key
45+
</button>
46+
{% endif %}
47+
</div>
3048
</div>
3149
</div>
3250

51+
<form action="{% url 'generate_api_key' %}" id="generate-api-key-form" method="post">{% csrf_token %}
52+
<button type="submit" class="btn btn-success">Generate</button>
53+
</form>
54+
55+
<form action="{% url 'revoke_api_key' %}" id="revoke-api-key-form" method="post">{% csrf_token %}
56+
<button type="submit" class="btn btn-danger">Revoke API key</button>
57+
</form>
58+
3359
</section>
3460
</div>
3561
{% endblock %}

scanpipe/tests/test_auth.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import uuid
2424

25+
from django.apps import apps
2526
from django.conf import settings
2627
from django.contrib.auth import get_user_model
2728
from django.contrib.auth.models import AnonymousUser
@@ -111,10 +112,12 @@ def test_scancodeio_auth_logout_view(self):
111112

112113
def test_scancodeio_account_profile_view(self):
113114
self.client.login(username=self.basic_user.username, password=TEST_PASSWORD)
115+
APIToken = apps.get_model("scanpipe", "APIToken")
116+
APIToken.create_token(user=self.basic_user)
114117
response = self.client.get(profile_url)
115118
expected = '<label class="label">API Key</label>'
116119
self.assertContains(response, expected, html=True)
117-
self.assertContains(response, self.basic_user.auth_token.key)
120+
# self.assertContains(response, self.basic_user.auth.key)
118121

119122
def test_scancodeio_auth_views_are_protected(self):
120123
a_uuid = uuid.uuid4()

scanpipe/views.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060

6161
import saneyaml
6262
import xlsxwriter
63+
from aboutcode.api_auth.views import BaseGenerateAPIKeyView
64+
from aboutcode.api_auth.views import BaseRevokeAPIKeyView
6365
from django_filters.views import FilterView
6466
from django_htmx.http import HttpResponseClientRedirect
6567
from licensedcode.spans import Span
@@ -2835,3 +2837,18 @@ def get_context_data(self, **kwargs):
28352837
context["parent_path"] = "/".join(parent_segments)
28362838

28372839
return context
2840+
2841+
2842+
class GenerateAPIKeyView(ConditionalLoginRequired, BaseGenerateAPIKeyView):
2843+
success_url = reverse_lazy("account_profile")
2844+
success_message = (
2845+
"<strong>Copy your API key now, it will not be shown again:</strong>"
2846+
'<pre class="mt-2 p-3">'
2847+
'<i class="fa fa-key mr-3" aria-hidden="true"></i>{plain_key}'
2848+
"</pre>"
2849+
)
2850+
2851+
2852+
class RevokeAPIKeyView(ConditionalLoginRequired, BaseRevokeAPIKeyView):
2853+
success_url = reverse_lazy("account_profile")
2854+
success_message = "API key revoked."

0 commit comments

Comments
 (0)