Merge branch 'master' into version-2021.3
# Conflicts: # web/src/constants.ts
10
README.md
|
@ -1,4 +1,6 @@
|
|||
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo">
|
||||
<p align="center">
|
||||
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
|
@ -22,8 +24,10 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum
|
|||
|
||||
## Screenshots
|
||||
|
||||
![](https://goauthentik.io/img/screen_apps.png)
|
||||
![](https://goauthentik.io/img/screen_admin.png)
|
||||
Light | Dark
|
||||
--- | ---
|
||||
![](https://goauthentik.io/img/screen_apps_light.png) | ![](https://goauthentik.io/img/screen_apps_dark.png)
|
||||
![](https://goauthentik.io/img/screen_admin_light.png) | ![](https://goauthentik.io/img/screen_admin_dark.png)
|
||||
|
||||
## Development
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
| Version | Supported |
|
||||
| ---------- | ------------------ |
|
||||
| 0.13.x | :white_check_mark: |
|
||||
| 0.14.x | :white_check_mark: |
|
||||
| 2021.1.x | :white_check_mark: |
|
||||
| 2021.2.x | :white_check_mark: |
|
||||
| 2021.3.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@
|
|||
</div>
|
||||
</section>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" />
|
||||
<ak-spinner-button form="main-form">
|
||||
{% block action %}{% endblock %}
|
||||
</ak-spinner-button>
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework.response import Response
|
|||
class Pagination(pagination.PageNumberPagination):
|
||||
"""Pagination which includes total pages and current page"""
|
||||
|
||||
page_query_param = "page"
|
||||
page_size_query_param = "page_size"
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
|
|
@ -90,7 +90,7 @@ class UserManager(DjangoUserManager):
|
|||
|
||||
|
||||
class User(GuardianUserMixin, AbstractUser):
|
||||
"""Custom User model to allow easier adding o f user-based settings"""
|
||||
"""Custom User model to allow easier adding of user-based settings"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||
name = models.TextField(help_text=_("User's display name."))
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "base/skeleton.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block body %}
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-page">
|
||||
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
|
||||
{% block page_content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base/page.html' %}
|
||||
{% extends 'base/skeleton.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.contrib.auth.mixins import (
|
|||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http.response import HttpResponse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import UpdateView
|
||||
from django.views.generic.base import TemplateView
|
||||
|
@ -35,7 +34,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
|||
form_class = UserDetailForm
|
||||
|
||||
success_message = _("Successfully updated user.")
|
||||
success_url = reverse_lazy("authentik_core:user-details")
|
||||
success_url = "/"
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
@ -62,7 +61,7 @@ class TokenCreateView(
|
|||
permission_required = "authentik_core.add_token"
|
||||
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully created Token")
|
||||
|
||||
def form_valid(self, form: UserTokenForm) -> HttpResponse:
|
||||
|
@ -80,7 +79,7 @@ class TokenUpdateView(
|
|||
form_class = UserTokenForm
|
||||
permission_required = "authentik_core.change_token"
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully updated Token")
|
||||
|
||||
def get_object(self) -> Token:
|
||||
|
@ -100,7 +99,7 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
|
|||
model = Token
|
||||
permission_required = "authentik_core.delete_token"
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully deleted Token")
|
||||
|
||||
def get_object(self) -> Token:
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.urls import NoReverseMatch
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils.http import urlencode
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
# Generated by Django 3.1.7 on 2021-03-02 08:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="eventmatcherpolicy",
|
||||
name="app",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("authentik.admin", "authentik Admin"),
|
||||
("authentik.api", "authentik API"),
|
||||
("authentik.events", "authentik Events"),
|
||||
("authentik.crypto", "authentik Crypto"),
|
||||
("authentik.flows", "authentik Flows"),
|
||||
("authentik.outposts", "authentik Outpost"),
|
||||
("authentik.lib", "authentik lib"),
|
||||
("authentik.policies", "authentik Policies"),
|
||||
("authentik.policies.dummy", "authentik Policies.Dummy"),
|
||||
(
|
||||
"authentik.policies.event_matcher",
|
||||
"authentik Policies.Event Matcher",
|
||||
),
|
||||
("authentik.policies.expiry", "authentik Policies.Expiry"),
|
||||
("authentik.policies.expression", "authentik Policies.Expression"),
|
||||
(
|
||||
"authentik.policies.group_membership",
|
||||
"authentik Policies.Group Membership",
|
||||
),
|
||||
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
|
||||
("authentik.policies.password", "authentik Policies.Password"),
|
||||
("authentik.policies.reputation", "authentik Policies.Reputation"),
|
||||
("authentik.providers.proxy", "authentik Providers.Proxy"),
|
||||
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
|
||||
("authentik.providers.saml", "authentik Providers.SAML"),
|
||||
("authentik.recovery", "authentik Recovery"),
|
||||
("authentik.sources.ldap", "authentik Sources.LDAP"),
|
||||
("authentik.sources.oauth", "authentik Sources.OAuth"),
|
||||
("authentik.sources.saml", "authentik Sources.SAML"),
|
||||
(
|
||||
"authentik.stages.authenticator_static",
|
||||
"authentik Stages.Authenticator.Static",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_totp",
|
||||
"authentik Stages.Authenticator.TOTP",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_validate",
|
||||
"authentik Stages.Authenticator.Validate",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_webauthn",
|
||||
"authentik Stages.Authenticator.WebAuthn",
|
||||
),
|
||||
("authentik.stages.captcha", "authentik Stages.Captcha"),
|
||||
("authentik.stages.consent", "authentik Stages.Consent"),
|
||||
("authentik.stages.deny", "authentik Stages.Deny"),
|
||||
("authentik.stages.dummy", "authentik Stages.Dummy"),
|
||||
("authentik.stages.email", "authentik Stages.Email"),
|
||||
(
|
||||
"authentik.stages.identification",
|
||||
"authentik Stages.Identification",
|
||||
),
|
||||
("authentik.stages.invitation", "authentik Stages.User Invitation"),
|
||||
("authentik.stages.password", "authentik Stages.Password"),
|
||||
("authentik.stages.prompt", "authentik Stages.Prompt"),
|
||||
("authentik.stages.user_delete", "authentik Stages.User Delete"),
|
||||
("authentik.stages.user_login", "authentik Stages.User Login"),
|
||||
("authentik.stages.user_logout", "authentik Stages.User Logout"),
|
||||
("authentik.stages.user_write", "authentik Stages.User Write"),
|
||||
("authentik.managed", "authentik Managed"),
|
||||
("authentik.core", "authentik Core"),
|
||||
],
|
||||
default="",
|
||||
help_text="Match events created by selected application. When left empty, all applications are matched.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -62,11 +62,15 @@ class PolicyAccessView(AccessMixin, View):
|
|||
return self.handle_no_permission()
|
||||
try:
|
||||
self.resolve_provider_application()
|
||||
except (Application.DoesNotExist, Provider.DoesNotExist):
|
||||
return self.handle_no_permission_authenticated()
|
||||
except (Application.DoesNotExist, Provider.DoesNotExist) as exc:
|
||||
LOGGER.warning("failed to resolve application", exc=exc)
|
||||
return self.handle_no_permission_authenticated(
|
||||
PolicyResult(False, _("Failed to resolve application"))
|
||||
)
|
||||
# Check if user is unauthenticated, so we pass the application
|
||||
# for the identification stage
|
||||
if not request.user.is_authenticated:
|
||||
LOGGER.warning("user not authenticated")
|
||||
return self.handle_no_permission()
|
||||
# Check permissions
|
||||
result = self.user_has_access()
|
||||
|
|
|
@ -45,6 +45,7 @@ class OAuth2ProviderSetupURLs(Serializer):
|
|||
token = ReadOnlyField()
|
||||
user_info = ReadOnlyField()
|
||||
provider_info = ReadOnlyField()
|
||||
logout = ReadOnlyField()
|
||||
|
||||
def create(self, request: Request) -> Response:
|
||||
raise NotImplementedError
|
||||
|
@ -83,6 +84,7 @@ class OAuth2ProviderViewSet(ModelViewSet):
|
|||
)
|
||||
),
|
||||
"provider_info": None,
|
||||
"logout": None,
|
||||
}
|
||||
try:
|
||||
data["provider_info"] = request.build_absolute_uri(
|
||||
|
@ -91,6 +93,12 @@ class OAuth2ProviderViewSet(ModelViewSet):
|
|||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
data["logout"] = request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_oauth2:end-session",
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
|
||||
pass
|
||||
return Response(data)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
"""Sync LDAP Users into authentik"""
|
||||
from datetime import datetime
|
||||
|
||||
import ldap3
|
||||
import ldap3.core.exceptions
|
||||
from django.db.utils import IntegrityError
|
||||
from pytz import UTC
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
|
||||
|
@ -53,11 +56,21 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
)
|
||||
)
|
||||
else:
|
||||
if created:
|
||||
ak_user.set_unusable_password()
|
||||
ak_user.save()
|
||||
self._logger.debug(
|
||||
"Synced User", user=ak_user.username, created=created
|
||||
)
|
||||
user_count += 1
|
||||
# pylint: disable=no-value-for-parameter
|
||||
pwd_last_set = UTC.localize(
|
||||
attributes.get("pwdLastSet", datetime.now())
|
||||
)
|
||||
if created or pwd_last_set >= ak_user.password_change_date:
|
||||
self._logger.debug(
|
||||
"Reset user's password",
|
||||
user=ak_user.username,
|
||||
created=created,
|
||||
pwd_last_set=pwd_last_set,
|
||||
)
|
||||
ak_user.set_unusable_password()
|
||||
ak_user.save()
|
||||
return user_count
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
"""Webauthn stage forms"""
|
||||
from django import forms
|
||||
|
||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
WebAuthnDevice,
|
||||
)
|
||||
|
||||
|
||||
class AuthenticateWebAuthnStageForm(forms.ModelForm):
|
||||
|
@ -15,3 +18,16 @@ class AuthenticateWebAuthnStageForm(forms.ModelForm):
|
|||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceEditForm(forms.ModelForm):
|
||||
"""Form to edit webauthn device"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = WebAuthnDevice
|
||||
fields = ["name"]
|
||||
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
|
|
@ -79,3 +79,8 @@ class WebAuthnDevice(Device):
|
|||
|
||||
def __str__(self):
|
||||
return self.name or str(self.user)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _("WebAuthn Device")
|
||||
verbose_name_plural = _("WebAuthn Devices")
|
||||
|
|
|
@ -17,6 +17,20 @@
|
|||
Created {{ created_on }}
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<div class="pf-c-data-list__cell">
|
||||
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-update' pk=device.pk %}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
{% trans 'Update' %}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-delete' pk=device.pk %}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
{% trans 'Delete' %}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
"""WebAuthn urls"""
|
||||
from django.urls import path
|
||||
|
||||
from authentik.stages.authenticator_webauthn.views import UserSettingsView
|
||||
from authentik.stages.authenticator_webauthn.views import (
|
||||
DeviceDeleteView,
|
||||
DeviceUpdateView,
|
||||
UserSettingsView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"<uuid:stage_uuid>/settings/", UserSettingsView.as_view(), name="user-settings"
|
||||
),
|
||||
path("devices/<int:pk>/delete/", DeviceDeleteView.as_view(), name="device-delete"),
|
||||
path("devices/<int:pk>/update/", DeviceUpdateView.as_view(), name="device-update"),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
"""webauthn views"""
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http.response import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import TemplateView
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import TemplateView, UpdateView
|
||||
|
||||
from authentik.admin.views.utils import DeleteMessageView
|
||||
from authentik.stages.authenticator_webauthn.forms import DeviceEditForm
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
WebAuthnDevice,
|
||||
|
@ -22,3 +27,34 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
|
|||
)
|
||||
kwargs["stage"] = stage
|
||||
return kwargs
|
||||
|
||||
|
||||
class DeviceUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||
"""Update device"""
|
||||
|
||||
model = WebAuthnDevice
|
||||
form_class = DeviceEditForm
|
||||
template_name = "generic/update.html"
|
||||
success_url = "/"
|
||||
success_message = _("Successfully updated Device")
|
||||
|
||||
def get_object(self) -> WebAuthnDevice:
|
||||
device: WebAuthnDevice = super().get_object()
|
||||
if device.user != self.request.user:
|
||||
raise Http404
|
||||
return device
|
||||
|
||||
|
||||
class DeviceDeleteView(LoginRequiredMixin, DeleteMessageView):
|
||||
"""Delete device"""
|
||||
|
||||
model = WebAuthnDevice
|
||||
template_name = "generic/delete.html"
|
||||
success_url = "/"
|
||||
success_message = _("Successfully deleted Device")
|
||||
|
||||
def get_object(self) -> WebAuthnDevice:
|
||||
device: WebAuthnDevice = super().get_object()
|
||||
if device.user != self.request.user:
|
||||
raise Http404
|
||||
return device
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
{% extends "base/page.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
{% block body %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans 'Reset your password' %}
|
||||
|
@ -14,4 +11,3 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -9657,7 +9657,7 @@ definitions:
|
|||
readOnly: true
|
||||
readOnly: true
|
||||
user:
|
||||
description: Custom User model to allow easier adding o f user-based settings
|
||||
description: Custom User model to allow easier adding of user-based settings
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
|
@ -10684,6 +10684,10 @@ definitions:
|
|||
title: Provider info
|
||||
type: string
|
||||
readOnly: true
|
||||
logout:
|
||||
title: Logout
|
||||
type: string
|
||||
readOnly: true
|
||||
ProxyProvider:
|
||||
description: ProxyProvider Serializer
|
||||
required:
|
||||
|
@ -11867,7 +11871,7 @@ definitions:
|
|||
description: Optional fixed data to enforce on user enrollment.
|
||||
type: object
|
||||
created_by:
|
||||
description: Custom User model to allow easier adding o f user-based settings
|
||||
description: Custom User model to allow easier adding of user-based settings
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
|
|
|
@ -4,7 +4,9 @@ import { NotFoundError, RequestError } from "./Error";
|
|||
export const VERSION = "v2beta";
|
||||
|
||||
export interface QueryArguments {
|
||||
[key: string]: number | string | boolean | null;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
[key: string]: number | string | boolean | undefined | null;
|
||||
}
|
||||
|
||||
export interface BaseInheritanceModel {
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface OAuth2SetupURLs {
|
|||
token: string;
|
||||
user_info: string;
|
||||
provider_info?: string;
|
||||
logout?: string;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -61,11 +61,6 @@ select[multiple] {
|
|||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* Fix pre elements within alerts */
|
||||
.pf-c-alert pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.pf-c-content h1 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
@ -85,6 +80,12 @@ select[multiple] {
|
|||
z-index: auto !important;
|
||||
}
|
||||
|
||||
/* ensure background on non-flow pages match */
|
||||
.pf-c-background-image::before {
|
||||
background-image: url("dist/assets/images/flow_background.jpg");
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* Fix spacing between messages */
|
||||
ak-message {
|
||||
display: block;
|
||||
|
|
|
@ -4,3 +4,4 @@ export const ERROR_CLASS = "pf-m-danger";
|
|||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2021.3.1-rc1";
|
||||
export const PAGE_SIZE = 20;
|
||||
|
|
|
@ -59,9 +59,9 @@ export class SpinnerButton extends LitElement {
|
|||
return;
|
||||
}
|
||||
if (this.form) {
|
||||
// Because safari we can't just extend HTMLButtonElement, hence I have to implement
|
||||
// these attributes by myself here, sigh...
|
||||
document.querySelector<HTMLFormElement>(`#${this.form}`)?.submit();
|
||||
// Since the form= attribute is only used within a modal button,
|
||||
// we can assume the form is always two levels up
|
||||
this.parentElement?.parentElement?.querySelector < HTMLFormElement>(`#${this.form}`)?.dispatchEvent(new Event("submit"));
|
||||
}
|
||||
this.setLoading();
|
||||
}
|
||||
|
|
|
@ -40,9 +40,7 @@ export class NotificationDrawer extends LitElement {
|
|||
}
|
||||
|
||||
renderItem(item: Notification): TemplateResult {
|
||||
const delta = Date.now() - (parseInt(item.created, 10) * 1000);
|
||||
// TODO: more flexible display, minutes and seconds
|
||||
const age = `${Math.round(delta / 1000 / 3600)} Hours ago`;
|
||||
const created = new Date(parseInt(item.created, 10) * 1000);
|
||||
let level = "";
|
||||
switch (item.severity) {
|
||||
case "notice":
|
||||
|
@ -76,7 +74,7 @@ export class NotificationDrawer extends LitElement {
|
|||
</button>
|
||||
</div>
|
||||
<p class="pf-c-notification-drawer__list-item-description">${item.body}</p>
|
||||
<small class="pf-c-notification-drawer__list-item-timestamp">${age}</small>
|
||||
<small class="pf-c-notification-drawer__list-item-timestamp">${created.toLocaleString()}</small>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import "../../elements/buttons/SpinnerButton";
|
|||
import "../../elements/buttons/Dropdown";
|
||||
import { Policy } from "../../api/Policies";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
|
@ -22,6 +23,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { TablePage } from "../../elements/table/TablePage";
|
|||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-application-list")
|
||||
export class ApplicationListPage extends TablePage<Application> {
|
||||
|
@ -30,6 +31,7 @@ export class ApplicationListPage extends TablePage<Application> {
|
|||
return Application.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { CertificateKeyPair } from "../../api/CertificateKeyPair";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-crypto-certificatekeypair-list")
|
||||
export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
|
@ -32,6 +33,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
|||
return CertificateKeyPair.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { gettext } from "django";
|
|||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { Event } from "../../api/Events";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { TablePage } from "../../elements/table/TablePage";
|
||||
import { time } from "../../utils";
|
||||
|
@ -31,6 +32,7 @@ export class EventListPage extends TablePage<Event> {
|
|||
return Event.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE * 3,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Rule } from "../../api/EventRules";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-event-rule-list")
|
||||
export class RuleListPage extends TablePage<Rule> {
|
||||
|
@ -33,6 +34,7 @@ export class RuleListPage extends TablePage<Rule> {
|
|||
return Rule.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Transport } from "../../api/EventTransports";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-event-transport-list")
|
||||
export class TransportListPage extends TablePage<Transport> {
|
||||
|
@ -31,6 +32,7 @@ export class TransportListPage extends TablePage<Transport> {
|
|||
return Transport.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import "../../elements/buttons/Dropdown";
|
|||
import "../../elements/policies/BoundPoliciesList";
|
||||
import { FlowStageBinding, Stage } from "../../api/Flows";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-bound-stages-list")
|
||||
export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
|
@ -24,6 +25,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
|||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { TablePage } from "../../elements/table/TablePage";
|
|||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-flow-list")
|
||||
export class FlowListPage extends TablePage<Flow> {
|
||||
|
@ -30,6 +31,7 @@ export class FlowListPage extends TablePage<Flow> {
|
|||
return Flow.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Group } from "../../api/Groups";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-group-list")
|
||||
export class GroupListPage extends TablePage<Group> {
|
||||
|
@ -30,6 +31,7 @@ export class GroupListPage extends TablePage<Group> {
|
|||
return Group.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import "./OutpostHealth";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/TokenCopyButton";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-outpost-list")
|
||||
export class OutpostListPage extends TablePage<Outpost> {
|
||||
|
@ -29,6 +30,7 @@ export class OutpostListPage extends TablePage<Outpost> {
|
|||
return Outpost.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import "../../elements/buttons/SpinnerButton";
|
|||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/Dropdown";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-outpost-service-connection-list")
|
||||
export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceConnection> {
|
||||
|
@ -31,6 +32,7 @@ export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceCo
|
|||
return OutpostServiceConnection.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
|
|||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Policy } from "../../api/Policies";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-policy-list")
|
||||
export class PolicyListPage extends TablePage<Policy> {
|
||||
|
@ -31,7 +32,8 @@ export class PolicyListPage extends TablePage<Policy> {
|
|||
apiEndpoint(page: number): Promise<AKResponse<Policy>> {
|
||||
return Policy.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import "../../elements/buttons/Dropdown";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-property-mapping-list")
|
||||
export class PropertyMappingListPage extends TablePage<PropertyMapping> {
|
||||
|
@ -35,6 +36,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
|
|||
return PropertyMapping.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
managed__isnull: this.hideManaged,
|
||||
});
|
||||
|
|
|
@ -148,10 +148,16 @@ export class OAuth2ProviderViewPage extends Page {
|
|||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
||||
<span class="pf-c-form__label-text">${gettext("Userinfo Endpoint")}</span>
|
||||
<span class="pf-c-form__label-text">${gettext("Userinfo URL")}</span>
|
||||
</label>
|
||||
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.user_info || "-"}" />
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
||||
<span class="pf-c-form__label-text">${gettext("Logout URL")}</span>
|
||||
</label>
|
||||
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.logout || "-"}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
|
|||
import "../../elements/buttons/Dropdown";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-provider-list")
|
||||
export class ProviderListPage extends TablePage<Provider> {
|
||||
|
@ -32,6 +33,7 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||
return Provider.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/buttons/Dropdown";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-source-list")
|
||||
export class SourceListPage extends TablePage<Source> {
|
||||
|
@ -32,6 +33,7 @@ export class SourceListPage extends TablePage<Source> {
|
|||
return Source.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Invitation } from "../../api/Invitations";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-stage-invitation-list")
|
||||
export class InvitationListPage extends TablePage<Invitation> {
|
||||
|
@ -30,6 +31,7 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||
return Invitation.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
|
|||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Prompt } from "../../api/Prompts";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-stage-prompt-list")
|
||||
export class PromptListPage extends TablePage<Prompt> {
|
||||
|
@ -30,6 +31,7 @@ export class PromptListPage extends TablePage<Prompt> {
|
|||
return Prompt.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
|
|||
import "../../elements/buttons/Dropdown";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { Stage } from "../../api/Flows";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-stage-list")
|
||||
export class StageListPage extends TablePage<Stage> {
|
||||
|
@ -32,6 +33,7 @@ export class StageListPage extends TablePage<Stage> {
|
|||
return Stage.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../../elements/buttons/Dropdown";
|
|||
import "../../elements/buttons/TokenCopyButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Token } from "../../api/Tokens";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-token-list")
|
||||
export class TokenListPage extends TablePage<Token> {
|
||||
|
@ -31,6 +32,7 @@ export class TokenListPage extends TablePage<Token> {
|
|||
return Token.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import "../../elements/buttons/Dropdown";
|
|||
import "../../elements/buttons/TokenCopyButton";
|
||||
import { Table, TableColumn } from "../../elements/table/Table";
|
||||
import { Token } from "../../api/Tokens";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-token-user-list")
|
||||
export class UserTokenList extends Table<Token> {
|
||||
|
@ -21,6 +22,7 @@ export class UserTokenList extends Table<Token> {
|
|||
return Token.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
@ -35,6 +37,18 @@ export class UserTokenList extends Table<Token> {
|
|||
];
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`
|
||||
<ak-modal-button href="-/user/tokens/create/">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Create")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
}
|
||||
|
||||
row(item: Token): TemplateResult[] {
|
||||
return [
|
||||
html`${item.identifier}`,
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../../elements/buttons/Dropdown";
|
|||
import "../../elements/buttons/ActionButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { User } from "../../api/Users";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
|
||||
@customElement("ak-user-list")
|
||||
export class UserListPage extends TablePage<User> {
|
||||
|
@ -31,6 +32,7 @@ export class UserListPage extends TablePage<User> {
|
|||
return User.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
page_size: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,5 +13,7 @@ See [Docker-compose](installation/docker-compose) or [Kubernetes](installation/k
|
|||
|
||||
## Screenshots
|
||||
|
||||
![](/img/screen_apps.png)
|
||||
![](/img/screen_admin.png)
|
||||
Light | Dark
|
||||
--- | ---
|
||||
![](/img/screen_apps_light.png) | ![](/img/screen_apps_dark.png)
|
||||
![](/img/screen_admin_light.png) | ![](/img/screen_admin_dark.png)
|
||||
|
|
|
@ -123,7 +123,7 @@ The integrations affected are:
|
|||
|
||||
### docker-compose
|
||||
|
||||
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.1/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
|
||||
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.2/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ This release does not introduce any new requirements.
|
|||
|
||||
### docker-compose
|
||||
|
||||
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.1/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
|
||||
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.3/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.6 KiB |
|
@ -100,7 +100,7 @@ function Home() {
|
|||
<div className="row">
|
||||
<div className="col col--5">
|
||||
<div>
|
||||
<img className={styles.featureImage} src={useBaseUrl('img/screen_apps.png')} alt="library screenshot"/>
|
||||
<img className={styles.featureImage} src={useBaseUrl('img/screen_apps_light.png')} alt="library screenshot"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col col--5 col--offset-2 padding-vert--xl">
|
||||
|
@ -131,7 +131,7 @@ function Home() {
|
|||
</div>
|
||||
<div className="col col--5">
|
||||
<div>
|
||||
<img className={styles.featureImage} src={useBaseUrl('img/screen_admin.png')} alt="library screenshot" />
|
||||
<img className={styles.featureImage} src={useBaseUrl('img/screen_admin_light.png')} alt="library screenshot" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Before Width: | Height: | Size: 331 KiB |
After Width: | Height: | Size: 399 KiB |
After Width: | Height: | Size: 382 KiB |
Before Width: | Height: | Size: 534 KiB |
After Width: | Height: | Size: 747 KiB |
After Width: | Height: | Size: 747 KiB |