wip: rename to authentik (#361)
* root: initial rename * web: rename custom element prefix * root: rename external functions with pb_ prefix * root: fix formatting * root: replace domain with goauthentik.io * proxy: update path * root: rename remaining prefixes * flows: rename file extension * root: pbadmin -> akadmin * docs: fix image filenames * lifecycle: ignore migration files * ci: copy default config from current source before loading last tagged * *: new sentry dsn * tests: fix missing python3.9-dev package * root: add additional migrations for service accounts created by outposts * core: mark system-created service accounts with attribute * policies/expression: fix pb_ replacement not working * web: fix last linting errors, add lit-analyse * policies/expressions: fix lint errors * web: fix sidebar display on screens where not all items fit * proxy: attempt to fix proxy pipeline * proxy: use go env GOPATH to get gopath * lib: fix user_default naming inconsistency * docs: add upgrade docs * docs: update screenshots to use authentik * admin: fix create button on empty-state of outpost * web: fix modal submit not refreshing SiteShell and Table * web: fix height of app-card and height of generic icon * web: fix rendering of subtext * admin: fix version check error not being caught * web: fix worker count not being shown * docs: update screenshots * root: new icon * web: fix lint error * admin: fix linting error * root: migrate coverage config to pyproject
This commit is contained in:
0
authentik/recovery/__init__.py
Normal file
0
authentik/recovery/__init__.py
Normal file
11
authentik/recovery/apps.py
Normal file
11
authentik/recovery/apps.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""authentik Recovery app config"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikRecoveryConfig(AppConfig):
|
||||
"""authentik Recovery app config"""
|
||||
|
||||
name = "authentik.recovery"
|
||||
label = "authentik_recovery"
|
||||
verbose_name = "authentik Recovery"
|
||||
mountpoint = "recovery/"
|
||||
0
authentik/recovery/management/__init__.py
Normal file
0
authentik/recovery/management/__init__.py
Normal file
0
authentik/recovery/management/commands/__init__.py
Normal file
0
authentik/recovery/management/commands/__init__.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""authentik recovery createkey command"""
|
||||
from datetime import timedelta
|
||||
from getpass import getuser
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Create Token used to recover access"""
|
||||
|
||||
help = _("Create a Key which can be used to restore access to authentik.")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"duration",
|
||||
default=1,
|
||||
action="store",
|
||||
help="How long the token is valid for (in years).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"user", action="store", help="Which user the Token gives access to."
|
||||
)
|
||||
|
||||
def get_url(self, token: Token) -> str:
|
||||
"""Get full recovery link"""
|
||||
return reverse("authentik_recovery:use-token", kwargs={"key": str(token.key)})
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Create Token used to recover access"""
|
||||
duration = int(options.get("duration", 1))
|
||||
_now = now()
|
||||
expiry = _now + timedelta(days=duration * 365.2425)
|
||||
user = User.objects.get(username=options.get("user"))
|
||||
token = Token.objects.create(
|
||||
expires=expiry,
|
||||
user=user,
|
||||
intent=TokenIntents.INTENT_RECOVERY,
|
||||
description=f"Recovery Token generated by {getuser()} on {_now}",
|
||||
)
|
||||
self.stdout.write(
|
||||
(
|
||||
f"Store this link safely, as it will allow"
|
||||
f" anyone to access authentik as {user}."
|
||||
)
|
||||
)
|
||||
self.stdout.write(self.get_url(token))
|
||||
34
authentik/recovery/tests.py
Normal file
34
authentik/recovery/tests.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""recovery tests"""
|
||||
from io import StringIO
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
|
||||
|
||||
class TestRecovery(TestCase):
|
||||
"""recovery tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(username="recovery-test-user")
|
||||
|
||||
def test_create_key(self):
|
||||
"""Test creation of a new key"""
|
||||
out = StringIO()
|
||||
self.assertEqual(len(Token.objects.all()), 0)
|
||||
call_command("create_recovery_key", "1", self.user.username, stdout=out)
|
||||
token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
|
||||
self.assertIn(token.key, out.getvalue())
|
||||
self.assertEqual(len(Token.objects.all()), 1)
|
||||
|
||||
def test_recovery_view(self):
|
||||
"""Test recovery view"""
|
||||
out = StringIO()
|
||||
call_command("create_recovery_key", "1", self.user.username, stdout=out)
|
||||
token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
|
||||
self.client.get(
|
||||
reverse("authentik_recovery:use-token", kwargs={"key": token.key})
|
||||
)
|
||||
self.assertEqual(int(self.client.session["_auth_user_id"]), token.user.pk)
|
||||
9
authentik/recovery/urls.py
Normal file
9
authentik/recovery/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""recovery views"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from authentik.recovery.views import UseTokenView
|
||||
|
||||
urlpatterns = [
|
||||
path("use-token/<str:key>/", UseTokenView.as_view(), name="use-token"),
|
||||
]
|
||||
24
authentik/recovery/views.py
Normal file
24
authentik/recovery/views.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""recovery views"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import login
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views import View
|
||||
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
|
||||
|
||||
class UseTokenView(View):
|
||||
"""Use token to login"""
|
||||
|
||||
def get(self, request: HttpRequest, key: str) -> HttpResponse:
|
||||
"""Check if token exists, log user in and delete token."""
|
||||
tokens = Token.filter_not_expired(key=key, intent=TokenIntents.INTENT_RECOVERY)
|
||||
if not tokens.exists():
|
||||
raise Http404
|
||||
token = tokens.first()
|
||||
login(request, token.user, backend="django.contrib.auth.backends.ModelBackend")
|
||||
token.delete()
|
||||
messages.warning(request, _("Used recovery-link to authenticate."))
|
||||
return redirect("authentik_core:shell")
|
||||
Reference in New Issue
Block a user