Compare commits

...

18 Commits

Author SHA1 Message Date
aef5c60a7b release: 0.13.0-rc3 2020-12-13 00:57:36 +01:00
d4c9c667c9 tests: fix URLs to use user-details instead of user-settings 2020-12-13 00:48:46 +01:00
96f0d582f0 core: load user detail form in an inner SiteShell so update doesn't reload entire page 2020-12-13 00:18:36 +01:00
7e8702a71e web: fix user detail form not working 2020-12-13 00:03:37 +01:00
1524061480 web: only auto-update slug when slug and name are already in sync 2020-12-12 23:45:47 +01:00
434922f702 web: make most client/network errors ignored by sentry 2020-12-12 23:32:55 +01:00
d2862ddc93 lifecycle: clean full redis as part of system migration 2020-12-12 23:30:49 +01:00
6e55431d4c stages/*: fix redirects not pointing to user_settings 2020-12-12 23:14:07 +01:00
01548c5e9c stages/*: fix links opening in SiteShell 2020-12-12 23:14:02 +01:00
bf1dae2dbe helm: make imagePullPolicy configurable 2020-12-12 23:13:58 +01:00
59c93defcf release: 0.13.0-rc2 2020-12-12 21:50:10 +01:00
a2a1a27502 web: fix icons not being included in static container 2020-12-12 21:49:00 +01:00
e3227e7d54 core: remove remaining references to old font 2020-12-12 21:41:12 +01:00
1f4a8fffdb docs: fix minor markdown and syntax errors 2020-12-12 21:30:05 +01:00
86b1183883 helm: bump version in readme 2020-12-12 21:27:05 +01:00
f781f4848c ci: fix release not depending on proxy build 2020-12-12 21:10:13 +01:00
19824d693c core: fix permission check for applications API 2020-12-12 21:00:35 +01:00
0694b911a4 docs: add changelog for 0.13 2020-12-12 21:00:23 +01:00
50 changed files with 250 additions and 137 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.13.0-rc1 current_version = 0.13.0-rc3
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
@ -23,6 +23,8 @@ values =
[bumpversion:file:helm/values.yaml] [bumpversion:file:helm/values.yaml]
[bumpversion:file:helm/README.md]
[bumpversion:file:helm/Chart.yaml] [bumpversion:file:helm/Chart.yaml]
[bumpversion:file:.github/workflows/release.yml] [bumpversion:file:.github/workflows/release.yml]

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image - name: Building Docker Image
run: docker build run: docker build
--no-cache --no-cache
-t beryju/authentik:0.13.0-rc1 -t beryju/authentik:0.13.0-rc3
-t beryju/authentik:latest -t beryju/authentik:latest
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:0.13.0-rc1 run: docker push beryju/authentik:0.13.0-rc3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest run: docker push beryju/authentik:latest
build-proxy: build-proxy:
@ -48,11 +48,11 @@ jobs:
cd proxy/ cd proxy/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-proxy:0.13.0-rc1 \ -t beryju/authentik-proxy:0.13.0-rc3 \
-t beryju/authentik-proxy:latest \ -t beryju/authentik-proxy:latest \
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:0.13.0-rc1 run: docker push beryju/authentik-proxy:0.13.0-rc3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest run: docker push beryju/authentik-proxy:latest
build-static: build-static:
@ -69,17 +69,18 @@ jobs:
cd web/ cd web/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-static:0.13.0-rc1 \ -t beryju/authentik-static:0.13.0-rc3 \
-t beryju/authentik-static:latest \ -t beryju/authentik-static:latest \
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:0.13.0-rc1 run: docker push beryju/authentik-static:0.13.0-rc3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest run: docker push beryju/authentik-static:latest
test-release: test-release:
needs: needs:
- build-server - build-server
- build-static - build-static
- build-proxy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
@ -106,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
tagName: 0.13.0-rc1 tagName: 0.13.0-rc3
environment: beryjuorg-prod environment: beryjuorg-prod

View File

@ -1,4 +1,4 @@
<img src="icons/icon_top_brand.svg" height="250" alt="authentik logo"> <img src="web/icons/icon_top_brand.svg" height="250" alt="authentik logo">
--- ---

View File

@ -1,2 +1,2 @@
"""authentik""" """authentik"""
__version__ = "0.13.0-rc1" __version__ = "0.13.0-rc3"

View File

@ -1,7 +1,10 @@
"""Application API Views""" """Application API Views"""
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http.response import Http404
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.generics import get_object_or_404
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
@ -71,8 +74,12 @@ class ApplicationViewSet(ModelViewSet):
@action(detail=True) @action(detail=True)
def metrics(self, request: Request, slug: str): def metrics(self, request: Request, slug: str):
"""Metrics for application logins""" """Metrics for application logins"""
# TODO: Check app read and audit read perms app = get_object_or_404(
app = Application.objects.get(slug=slug) get_objects_for_user(request.user, "authentik_core.view_application"),
slug=slug,
)
if not request.user.has_perm("authentik_audit.view_event"):
raise Http404
return Response( return Response(
get_events_per_1h( get_events_per_1h(
action=EventAction.AUTHORIZE_APPLICATION, action=EventAction.AUTHORIZE_APPLICATION,

View File

@ -6,8 +6,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<link rel="preload" href="{% static 'dist/assets/fonts/DINEngschriftStd.woff2' %}" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{% static 'dist/assets/fonts/DINEngschriftStd.woff' %}" as="font" type="font/woff" crossorigin>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title> <title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>

View File

@ -0,0 +1,26 @@
{% load i18n %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% trans 'Update details' %}
</div>
<div class="pf-c-card__body">
<form action="" method="post" class="pf-c-form pf-m-horizontal">
{% include 'partials/form_horizontal.html' with form=form %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-primary" type="submit" value="{% trans 'Update' %}" />
{% if unenrollment_enabled %}
<a class="pf-c-button pf-m-danger"
href="{% url 'authentik_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{%
trans "Delete account" %}</a>
{% endif %}
</div>
</div>
</div>
</form>
</div>
</div>

View File

@ -15,29 +15,9 @@
<section class="pf-c-page__main-section"> <section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center"> <div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75"> <div class="pf-u-w-75">
<div class="pf-c-card"> <ak-site-shell url="{% url 'authentik_core:user-details' %}">
<div class="pf-c-card__header pf-c-title pf-m-md"> <div slot="body"></div>
{% trans 'Update details' %} </ak-site-shell>
</div>
<div class="pf-c-card__body">
<form action="" method="post" class="pf-c-form pf-m-horizontal">
{% include 'partials/form_horizontal.html' with form=form %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-primary" type="submit" value="{% trans 'Update' %}" />
{% if unenrollment_enabled %}
<a class="pf-c-button pf-m-danger"
href="{% url 'authentik_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% trans "Delete account" %}</a>
{% endif %}
</div>
</div>
</div>
</form>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -34,9 +34,3 @@ class TestOverviewViews(TestCase):
self.assertEqual( self.assertEqual(
self.client.get(reverse("authentik_core:overview")).status_code, 200 self.client.get(reverse("authentik_core:overview")).status_code, 200
) )
def test_user_settings(self):
"""Test user settings"""
self.assertEqual(
self.client.get(reverse("authentik_core:user-settings")).status_code, 200
)

View File

@ -28,3 +28,9 @@ class TestUserViews(TestCase):
self.assertEqual( self.assertEqual(
self.client.get(reverse("authentik_core:user-settings")).status_code, 200 self.client.get(reverse("authentik_core:user-settings")).status_code, 200
) )
def test_user_details(self):
"""Test UserDetailsView"""
self.assertEqual(
self.client.get(reverse("authentik_core:user-details")).status_code, 200
)

View File

@ -7,6 +7,7 @@ urlpatterns = [
path("", shell.ShellView.as_view(), name="shell"), path("", shell.ShellView.as_view(), name="shell"),
# User views # User views
path("-/user/", user.UserSettingsView.as_view(), name="user-settings"), path("-/user/", user.UserSettingsView.as_view(), name="user-settings"),
path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"),
path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"), path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"),
path( path(
"-/user/tokens/create/", "-/user/tokens/create/",

View File

@ -11,6 +11,7 @@ from django.http.response import HttpResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from django.views.generic.base import TemplateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
@ -26,14 +27,20 @@ from authentik.flows.models import Flow, FlowDesignation
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): class UserSettingsView(TemplateView):
"""Update User settings""" """Multiple SiteShells for user details and all stages"""
template_name = "user/settings.html" template_name = "user/settings.html"
class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
"""Update User details"""
template_name = "user/details.html"
form_class = UserDetailForm form_class = UserDetailForm
success_message = _("Successfully updated user.") success_message = _("Successfully updated user.")
success_url = reverse_lazy("authentik_core:user-settings") success_url = reverse_lazy("authentik_core:user-details")
def get_object(self): def get_object(self):
return self.request.user return self.request.user

View File

@ -22,10 +22,10 @@
</ul> </ul>
{% if not state %} {% if not state %}
{% if stage.configure_flow %} {% if stage.configure_flow %}
<a href="{% url 'authentik_flows:configure' stage_uuid=stage.stage_uuid %}?next={{ request.get_full_path }}" class="pf-c-button pf-m-primary">{% trans "Enable Static Tokens" %}</a> <a href="{% url 'authentik_flows:configure' stage_uuid=stage.stage_uuid %}?next={% url 'authentik_core:user-settings' %}" class="ak-root-link pf-c-button pf-m-primary">{% trans "Enable Static Tokens" %}</a>
{% endif %} {% endif %}
{% else %} {% else %}
<a href="{% url 'authentik_stages_otp_static:disable' stage_uuid=stage.stage_uuid %}" class="pf-c-button pf-m-danger">{% trans "Disable Static Tokens" %}</a> <a href="{% url 'authentik_stages_otp_static:disable' stage_uuid=stage.stage_uuid %}" class="ak-root-pf-c-button pf-m-danger">{% trans "Disable Static Tokens" %}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -41,4 +41,4 @@ class DisableView(LoginRequiredMixin, View):
Event.new( Event.new(
"static_otp_disable", message="User disabled Static OTP Tokens." "static_otp_disable", message="User disabled Static OTP Tokens."
).from_http(request) ).from_http(request)
return redirect("authentik_stages_otp:otp-user-settings") return redirect("authentik_core:user-settings")

View File

@ -18,10 +18,10 @@
<p> <p>
{% if not state %} {% if not state %}
{% if stage.configure_flow %} {% if stage.configure_flow %}
<a href="{% url 'authentik_flows:configure' stage_uuid=stage.stage_uuid %}?next={{ request.get_full_path }}" class="pf-c-button pf-m-primary">{% trans "Enable Time-based OTP" %}</a> <a href="{% url 'authentik_flows:configure' stage_uuid=stage.stage_uuid %}?next={% url 'authentik_core:user-settings' %}" class="ak-root-link pf-c-button pf-m-primary">{% trans "Enable Time-based OTP" %}</a>
{% endif %} {% endif %}
{% else %} {% else %}
<a href="{% url 'authentik_stages_otp_time:disable' stage_uuid=stage.stage_uuid %}" class="pf-c-button pf-m-danger">{% trans "Disable Time-based OTP" %}</a> <a href="{% url 'authentik_stages_otp_time:disable' stage_uuid=stage.stage_uuid %}" class="ak-root-pf-c-button pf-m-danger">{% trans "Disable Time-based OTP" %}</a>
{% endif %} {% endif %}
</p> </p>
</div> </div>

View File

@ -38,4 +38,4 @@ class DisableView(LoginRequiredMixin, View):
Event.new("totp_disable", message="User disabled Time-based OTP.").from_http( Event.new("totp_disable", message="User disabled Time-based OTP.").from_http(
request request
) )
return redirect("authentik_stages_otp:otp-user-settings") return redirect("authentik_core:user-settings")

View File

@ -9,7 +9,7 @@
{% trans 'Reset your password' %} {% trans 'Reset your password' %}
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<a class="pf-c-button pf-m-primary" href="{{ url }}"> <a class="pf-c-button pf-m-primary ak-root-link" href="{{ url }}">
{% trans 'Change password' %} {% trans 'Change password' %}
</a> </a>
</div> </div>

View File

@ -19,7 +19,7 @@ services:
networks: networks:
- internal - internal
server: server:
image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-rc1} image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-rc3}
command: server command: server
environment: environment:
AUTHENTIK_REDIS__HOST: redis AUTHENTIK_REDIS__HOST: redis
@ -42,7 +42,7 @@ services:
env_file: env_file:
- .env - .env
worker: worker:
image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-rc1} image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-rc3}
command: worker command: worker
networks: networks:
- internal - internal
@ -56,7 +56,7 @@ services:
env_file: env_file:
- .env - .env
static: static:
image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.0-rc1} image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.0-rc3}
networks: networks:
- internal - internal
labels: labels:

View File

@ -4,8 +4,8 @@ name: authentik
home: https://goauthentik.io home: https://goauthentik.io
sources: sources:
- https://github.com/BeryJu/authentik - https://github.com/BeryJu/authentik
version: "0.13.0-rc1" version: "0.13.0-rc3"
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/icons/icon.svg icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
dependencies: dependencies:
- name: postgresql - name: postgresql
version: 9.4.1 version: 9.4.1

View File

@ -4,7 +4,8 @@
|-----------------------------------|-------------------------|-------------| |-----------------------------------|-------------------------|-------------|
| image.name | beryju/authentik | Image used to run the authentik server and worker | | image.name | beryju/authentik | Image used to run the authentik server and worker |
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) | | image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
| image.tag | 0.12.5-stable | Image tag | | image.tag | 0.13.0-rc3 | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment | | serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment | | workerReplicas | 1 | Replicas for the Worker deployment |
| kubernetesIntegration | true | Enable/disable the Kubernetes integration for authentik. This will create a service account for authentik to create and update outposts in authentik | | kubernetesIntegration | true | Enable/disable the Kubernetes integration for authentik. This will create a service account for authentik to create and update outposts in authentik |

View File

@ -24,7 +24,7 @@ spec:
containers: containers:
- name: {{ .Chart.Name }}-static - name: {{ .Chart.Name }}-static
image: "{{ .Values.image.name_static }}:{{ .Values.image.tag }}" image: "{{ .Values.image.name_static }}:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports: ports:
- name: http - name: http
containerPort: 80 containerPort: 80

View File

@ -45,6 +45,7 @@ spec:
initContainers: initContainers:
- name: authentik-database-migrations - name: authentik-database-migrations
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}" image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
args: [migrate] args: [migrate]
envFrom: envFrom:
- configMapRef: - configMapRef:
@ -69,6 +70,7 @@ spec:
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}" image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
args: [server] args: [server]
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@ -48,7 +48,7 @@ spec:
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}" image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent imagePullPolicy: "{{ .Values.image.pullPolicy }}"
args: [worker] args: [worker]
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@ -1,5 +1,6 @@
image: image:
tag: gh-master tag: gh-master
pullPolicy: Always
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1

View File

@ -5,7 +5,8 @@ image:
name: beryju/authentik name: beryju/authentik
name_static: beryju/authentik-static name_static: beryju/authentik-static
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
tag: 0.13.0-rc1 tag: 0.13.0-rc3
pullPolicy: IfNotPresent
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1

View File

@ -1,4 +1,7 @@
# flake8: noqa # flake8: noqa
from redis import Redis
from authentik.lib.config import CONFIG
from lifecycle.migrate import BaseMigration from lifecycle.migrate import BaseMigration
SQL_STATEMENT = """BEGIN TRANSACTION; SQL_STATEMENT = """BEGIN TRANSACTION;
@ -103,3 +106,16 @@ class Migration(BaseMigration):
def run(self): def run(self):
self.cur.execute(SQL_STATEMENT) self.cur.execute(SQL_STATEMENT)
self.con.commit() self.con.commit()
# We also need to clean the cache to make sure no pickeled objects still exist
for db in [
CONFIG.y("redis.message_queue_db"),
CONFIG.y("redis.cache_db"),
CONFIG.y("redis.ws_db"),
]:
redis = Redis(
host=CONFIG.y("redis.host"),
port=6379,
db=db,
password=CONFIG.y("redis.password"),
)
redis.flushall()

View File

@ -1,3 +1,3 @@
package pkg package pkg
const VERSION = "0.13.0-rc1" const VERSION = "0.13.0-rc3"

View File

@ -142,7 +142,7 @@ class TestSourceOAuth2(SeleniumTestCase):
# Wait until we've logged in # Wait until we've logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
@ -224,7 +224,7 @@ class TestSourceOAuth2(SeleniumTestCase):
# Wait until we've logged in # Wait until we've logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
@ -317,7 +317,7 @@ class TestSourceOAuth1(SeleniumTestCase):
sleep(2) sleep(2)
# Wait until we've logged in # Wait until we've logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), self.driver.find_element(By.ID, "id_username").get_attribute("value"),

View File

@ -134,7 +134,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
# Wait until we've loaded the user info page # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(
@ -185,7 +185,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
# Wait until we've loaded the user info page # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(
@ -234,7 +234,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.shell_url("authentik_core:overview")) self.wait_for_url(self.shell_url("authentik_core:overview"))
self.driver.get(self.url("authentik_core:user-settings")) self.driver.get(self.url("authentik_core:user-details"))
# Wait until we've loaded the user info page # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -16,7 +16,7 @@ const resources = [
{ src: "src/index.html", dest: "dist" }, { src: "src/index.html", dest: "dist" },
{ src: "src/authentik.css", dest: "dist" }, { src: "src/authentik.css", dest: "dist" },
{ src: "src/assets/*", dest: "dist/assets" }, { src: "src/assets/*", dest: "dist/assets" },
{ src: "../icons/*", dest: "dist/assets/icons" }, { src: "./icons/*", dest: "dist/assets/icons" },
]; ];
export default [ export default [

View File

@ -2,6 +2,7 @@ import { DefaultClient } from "./client";
import * as Sentry from "@sentry/browser"; import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing"; import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants"; import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors";
export class Config { export class Config {
branding_logo: string; branding_logo: string;
@ -24,6 +25,12 @@ export class Config {
integrations: [new Integrations.BrowserTracing()], integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
environment: config.error_reporting_environment, environment: config.error_reporting_environment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
return event;
},
}); });
console.debug("authentik/config: Sentry enabled."); console.debug("authentik/config: Sentry enabled.");
} }

1
web/src/common/errors.ts Normal file
View File

@ -0,0 +1 @@
export class SentryIgnoredError extends Error {}

View File

@ -28,4 +28,4 @@ export const ColorStyles = css`
background-color: var(--pf-global--danger-color--100); background-color: var(--pf-global--danger-color--100);
} }
`; `;
export const VERSION = "0.13.0-rc1"; export const VERSION = "0.13.0-rc3";

View File

@ -1,5 +1,6 @@
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import Chart from "chart.js"; import Chart from "chart.js";
import { showMessage } from "./messages/MessageContainer";
interface TickValue { interface TickValue {
value: number; value: number;
@ -41,7 +42,13 @@ export class AdminLoginsChart extends LitElement {
firstUpdated(): void { firstUpdated(): void {
fetch(this.url) fetch(this.url)
.then((r) => r.json()) .then((r) => r.json())
.catch((e) => console.error(e)) .catch((e) => {
showMessage({
level_tag: "error",
message: "Unexpected error"
});
console.log(e);
})
.then((r) => { .then((r) => {
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas"); const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
if (!canvas) { if (!canvas) {

View File

@ -13,6 +13,7 @@ import fa from "@fortawesome/fontawesome-free/css/solid.css";
import { convertToSlug } from "../../utils"; import { convertToSlug } from "../../utils";
import { SpinnerButton } from "./SpinnerButton"; import { SpinnerButton } from "./SpinnerButton";
import { PRIMARY_CLASS } from "../../constants"; import { PRIMARY_CLASS } from "../../constants";
import { showMessage } from "../messages/MessageContainer";
@customElement("ak-modal-button") @customElement("ak-modal-button")
export class ModalButton extends LitElement { export class ModalButton extends LitElement {
@ -63,15 +64,21 @@ export class ModalButton extends LitElement {
}); });
// Make name field update slug field // Make name field update slug field
this.querySelectorAll<HTMLInputElement>("input[name=name]").forEach((input) => { this.querySelectorAll<HTMLInputElement>("input[name=name]").forEach((input) => {
const form = input.closest("form");
if (form === null) {
return;
}
const slugField = form.querySelector<HTMLInputElement>("input[name=slug]");
if (!slugField) {
return;
}
// Only attach handler if the slug is already equal to the name
// if not, they are probably completely different and shouldn't update
// each other
if (convertToSlug(input.value) !== slugField.value) {
return;
}
input.addEventListener("input", () => { input.addEventListener("input", () => {
const form = input.closest("form");
if (form === null) {
return;
}
const slugField = form.querySelector<HTMLInputElement>("input[name=slug]");
if (!slugField) {
return;
}
slugField.value = convertToSlug(input.value); slugField.value = convertToSlug(input.value);
}); });
}); });
@ -110,7 +117,11 @@ export class ModalButton extends LitElement {
} }
}) })
.catch((e) => { .catch((e) => {
console.error(e); showMessage({
level_tag: "error",
message: "Unexpected error"
});
console.log(e);
}); });
}); });
}); });
@ -139,7 +150,11 @@ export class ModalButton extends LitElement {
}); });
}) })
.catch((e) => { .catch((e) => {
console.error(e); showMessage({
level_tag: "error",
message: "Unexpected error"
});
console.log(e);
}); });
} }
} }

View File

@ -1,20 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<link
rel="preload"
href="/static/authentik/fonts/DINEngschriftStd.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/static/authentik/fonts/DINEngschriftStd.woff"
as="font"
type="font/woff"
crossorigin
/>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>authentik</title> <title>authentik</title>

View File

@ -1,4 +1,5 @@
import { LitElement, html, customElement, property, TemplateResult } from "lit-element"; import { LitElement, html, customElement, property, TemplateResult } from "lit-element";
import { SentryIgnoredError } from "../../common/errors";
enum ResponseType { enum ResponseType {
redirect = "redirect", redirect = "redirect",
@ -30,7 +31,7 @@ export class FlowShellCard extends LitElement {
// Fallback when the flow does not exist, just redirect to the root // Fallback when the flow does not exist, just redirect to the root
window.location.pathname = "/"; window.location.pathname = "/";
} else if (!r.ok) { } else if (!r.ok) {
throw Error(r.statusText); throw new SentryIgnoredError(r.statusText);
} }
return r; return r;
}) })

View File

@ -8,6 +8,7 @@ import BackdropStyle from "@patternfly/patternfly/components/Backdrop/backdrop.c
import { SpinnerSize } from "../../elements/Spinner"; import { SpinnerSize } from "../../elements/Spinner";
import { showMessage } from "../../elements/messages/MessageContainer"; import { showMessage } from "../../elements/messages/MessageContainer";
import { gettext } from "django"; import { gettext } from "django";
import { SentryIgnoredError } from "../../common/errors";
@customElement("ak-site-shell") @customElement("ak-site-shell")
export class SiteShell extends LitElement { export class SiteShell extends LitElement {
@ -55,9 +56,16 @@ export class SiteShell extends LitElement {
} }
loadContent(): void { loadContent(): void {
const bodySlot = this.querySelector("[slot=body]");
if (!bodySlot) {
return;
}
if (!this._url) { if (!this._url) {
return; return;
} }
if (this.loading) {
return;
}
this.loading = true; this.loading = true;
fetch(this._url) fetch(this._url)
.then((r) => { .then((r) => {
@ -70,52 +78,82 @@ export class SiteShell extends LitElement {
level_tag: "error", level_tag: "error",
message: gettext(`Request failed: ${r.statusText}`), message: gettext(`Request failed: ${r.statusText}`),
}); });
throw new Error("Request failed"); this.loading = false;
throw new SentryIgnoredError("Request failed");
}) })
.then((r) => r.text()) .then((r) => r.text())
.then((t) => { .then((text) => {
const bodySlot = this.querySelector("[slot=body]"); bodySlot.innerHTML = text;
if (!bodySlot) { this.updateHandlers();
return;
}
bodySlot.innerHTML = t;
}) })
.then(() => { .then(() => {
// Ensure anchors only change the hash
this.querySelectorAll<HTMLAnchorElement>("a:not(.ak-root-link)").forEach((a) => {
if (a.href === "") {
return;
}
try {
const url = new URL(a.href);
const qs = url.search || "";
a.href = `#${url.pathname}${qs}`;
} catch (e) {
console.debug(`authentik/site-shell: error ${e}`);
a.href = `#${a.href}`;
}
});
// Create refresh buttons
this.querySelectorAll("[role=ak-refresh]").forEach((rt) => {
rt.addEventListener("click", () => {
this.loadContent();
});
});
// Make get forms (search bar) notify us on submit so we can change the hash
this.querySelectorAll("form").forEach((f) => {
f.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(f);
const qs = new URLSearchParams((<any>formData)).toString(); // eslint-disable-line
window.location.hash = `#${this._url}?${qs}`;
});
});
setTimeout(() => { setTimeout(() => {
this.loading = false; this.loading = false;
}, 100); }, 100);
}); });
} }
updateHandlers(): void {
// Ensure anchors only change the hash
this.querySelectorAll<HTMLAnchorElement>("a:not(.ak-root-link)").forEach((a) => {
if (a.href === "") {
return;
}
try {
const url = new URL(a.href);
const qs = url.search || "";
a.href = `#${url.pathname}${qs}`;
} catch (e) {
console.debug(`authentik/site-shell: error ${e}`);
a.href = `#${a.href}`;
}
});
// Create refresh buttons
this.querySelectorAll("[role=ak-refresh]").forEach((rt) => {
rt.addEventListener("click", () => {
this.loadContent();
});
});
// Make get forms (search bar) notify us on submit so we can change the hash
this.querySelectorAll<HTMLFormElement>("form[method=get]").forEach((form) => {
form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
const qs = new URLSearchParams((<any>formData)).toString(); // eslint-disable-line
window.location.hash = `#${this._url}?${qs}`;
});
});
// Make forms with POST Method have a correct action set
this.querySelectorAll<HTMLFormElement>("form[method=post]").forEach((form) => {
form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
fetch(this._url ? this._url : form.action, {
method: form.method,
body: formData,
})
.then((response) => {
return response.text();
})
.then((data) => {
const bodySlot = this.querySelector("[slot=body]");
if (!bodySlot) {
return;
}
bodySlot.innerHTML = data;
this.updateHandlers();
})
.catch((e) => {
showMessage({
level_tag: "error",
message: "Unexpected error"
});
console.log(e);
});
});
});
}
render(): TemplateResult { render(): TemplateResult {
return html` ${this.loading ? return html` ${this.loading ?
html`<div class="pf-c-backdrop"> html`<div class="pf-c-backdrop">

View File

@ -15,7 +15,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.0-rc1 >> .env` To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.0-rc3 >> .env`
If this is a fresh authentik install run the following commands to generate a password: If this is a fresh authentik install run the following commands to generate a password:

View File

@ -22,7 +22,7 @@ image:
name: beryju/authentik name: beryju/authentik
name_static: beryju/authentik-static name_static: beryju/authentik-static
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
tag: 0.13.0-rc1 tag: 0.13.0-rc3
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1

View File

@ -4,6 +4,21 @@ title: Upgrading to 0.13 (passbook -> authentik)
After a long back and forth, we've finally switched to a more permanent name. Whilst the upgrade is pretty much seamless, there are some things you have to change before upgrading. After a long back and forth, we've finally switched to a more permanent name. Whilst the upgrade is pretty much seamless, there are some things you have to change before upgrading.
# Headline changes
- New name (https://github.com/BeryJu/authentik/pull/361)
- The web interface is now a semi-SPA Experience. This means that most operations are done through Asynchronous requests
In this initial release, this brings features such as a refresh button, a generally better User experience due to shorter loading times
and fewer visual context changes.
- The web interface now has a darkmode, which is enabled automatically based on your Operating system.
## Smaller changes
- Add better support for Docker Service Connections with Certificates
- Fix application API not returning the same format as other APIs
## Upgrading ## Upgrading
### docker-compose ### docker-compose
@ -30,7 +45,7 @@ helm repo remove passbook
helm repo add authentik https://docker.beryju.org/chartrepo/authentik helm repo add authentik https://docker.beryju.org/chartrepo/authentik
``` ```
:::notice :::note
If you've set any custom image names in your values file, make sure to change them to authentik before upgrading. If you've set any custom image names in your values file, make sure to change them to authentik before upgrading.
::: :::
@ -51,7 +66,7 @@ helm upgrade passbook authentik/authentik --devel -f values.yaml
- Some default values change, for example the SAML Provider's default issuer. - Some default values change, for example the SAML Provider's default issuer.
This only makes a difference for newly created objects. This only makes a difference for newly created providers.
- Expression Policies variables change - Expression Policies variables change