Compare commits

..

43 Commits

Author SHA1 Message Date
32e5ebb8a3 release: 2021.3.1 2021-03-03 20:53:43 +01:00
597e00dd86 root: update bumpversion config 2021-03-03 20:53:38 +01:00
dd31191845 Merge branch 'master' into version-2021.3 2021-03-03 20:48:02 +01:00
e9d95b1311 docs: Add Wiki.js integration docs (#610)
* docs(wikijs): Add Wiki.js integration docs

* docs(wikijs): Add to sidebar.
2021-03-03 20:31:02 +01:00
3319547a0e outposts: improve error handling for kubernetes outpost 2021-03-03 20:27:38 +01:00
1a00730cdd core: cleanup output for backup task 2021-03-03 20:11:55 +01:00
466723573c api: fix types for config API 2021-03-03 20:05:43 +01:00
ea784d47f4 admin: fix mismatched Swagger schema 2021-03-03 17:44:47 +01:00
77d5ba2862 events: fix typo in events API 2021-03-03 16:54:59 +01:00
f4580a1097 api: remove legacy messages API as its WS only 2021-03-03 15:02:20 +01:00
9e3d1f0baa web: fix circular dependency 2021-03-03 11:38:30 +01:00
c002c4b610 api: make pagination required 2021-03-03 10:37:03 +01:00
dde5e910cf root: fix name of docker images 2021-03-03 10:36:46 +01:00
5218332bce web: improve error handing for fetch in AdminLoginChart 2021-03-03 10:06:54 +01:00
28cd08bbba core: make user settings use vertical tabs 2021-03-03 10:05:12 +01:00
3cb0575a1e root: fix swagger pagination not matching API 2021-03-03 09:28:22 +01:00
dc1c1b9569 build(deps): bump boto3 from 1.17.18 to 1.17.19 (#609) 2021-03-03 08:58:24 +01:00
662d117b66 root: replace ghcr with harbor for expiry 2021-03-03 00:07:42 +01:00
b2449757f9 web/stages/authenticator_validate: only show back button when multiple challenges 2021-03-02 22:30:21 +01:00
a0753bfc88 docs: add docs for deny stage 2021-03-02 22:25:28 +01:00
e2a771bdaa docs: update screenshot in captcha stage 2021-03-02 22:25:00 +01:00
23de9df2a5 stages/authenticator_validate: cleanup 2021-03-02 22:20:54 +01:00
5c739ebed2 docs: add authenticator_webauthn stage docs 2021-03-02 22:20:05 +01:00
d3f8d7120f docs: cleanup, add 2021.3 to sidebar 2021-03-02 22:10:54 +01:00
21fd251edf docs: add apache guacamole integration 2021-03-02 22:04:53 +01:00
28cededb90 docs: update integration for harbor 2021-03-02 21:49:04 +01:00
d420719649 release: 2021.3.1-rc2 2021-03-02 21:41:30 +01:00
0018fbacd3 Merge branch 'master' into version-2021.3
# Conflicts:
#	web/src/constants.ts
2021-03-02 21:39:30 +01:00
8c41d2f4cb stages/authenticator_webauthn: add views to update and delete devices 2021-03-02 21:26:31 +01:00
3941590d0c web: fix missing create buttons on user token list 2021-03-02 21:16:14 +01:00
dc4a7c35da core: fix errors on user token views 2021-03-02 21:16:03 +01:00
e8c9b70ae8 sources/ldap: check pwdLastSet when syncing Users 2021-03-02 21:05:02 +01:00
74d240dfd4 admin: use spinner-button for modal forms 2021-03-02 20:37:23 +01:00
7d296b2119 root: align image on readme 2021-03-02 17:00:36 +01:00
373793ce9a policies: show more information when provider fails to resolve application 2021-03-02 16:58:55 +01:00
5c0ec7554b web: fix lists not being paginated 2021-03-02 15:12:26 +01:00
792fa45dca providers/oauth2: add logout URL to Setup URLs API 2021-03-02 15:11:18 +01:00
743aaea15e policies: improve logging 2021-03-02 15:04:31 +01:00
de03ed0aec web: fix background for shell without flow executor 2021-03-02 15:04:14 +01:00
e68ec16a34 web: improve display of notification age 2021-03-02 15:03:58 +01:00
68a0219d0f docs: update debug screenshot 2021-03-02 13:29:09 +01:00
38d9533afd root: update screenshots 2021-03-02 12:15:32 +01:00
7538af5e09 docs: fix download links for compose 2021-03-02 10:07:46 +01:00
132 changed files with 2620 additions and 1283 deletions

View File

@ -1,9 +1,11 @@
[bumpversion]
current_version = 2021.3.1-rc1
current_version = 2021.3.1
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
serialize = {major}.{minor}.{patch}-{release}
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
serialize =
{major}.{minor}.{patch}-{release}
{major}.{minor}.{patch}
message = release: {new_version}
tag_name = version/{new_version}

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/authentik:2021.3.1-rc1
-t beryju/authentik:2021.3.1
-t beryju/authentik:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:2021.3.1-rc1
run: docker push beryju/authentik:2021.3.1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest
build-proxy:
@ -48,11 +48,11 @@ jobs:
cd outpost/
docker build \
--no-cache \
-t beryju/authentik-proxy:2021.3.1-rc1 \
-t beryju/authentik-proxy:2021.3.1 \
-t beryju/authentik-proxy:latest \
-f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:2021.3.1-rc1
run: docker push beryju/authentik-proxy:2021.3.1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest
build-static:
@ -69,11 +69,11 @@ jobs:
cd web/
docker build \
--no-cache \
-t beryju/authentik-static:2021.3.1-rc1 \
-t beryju/authentik-static:2021.3.1 \
-t beryju/authentik-static:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:2021.3.1-rc1
run: docker push beryju/authentik-static:2021.3.1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest
test-release:
@ -107,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 2021.3.1-rc1
tagName: 2021.3.1
environment: beryjuorg-prod

24
Pipfile.lock generated
View File

@ -95,10 +95,10 @@
},
"autobahn": {
"hashes": [
"sha256:884f79c50fdc55ade2c315946a9caa145e8b10075eee9d2c2594ea5e8f5226aa",
"sha256:bf7a9d302a34d0f719d43c57f65ca1f2f5c982dd6ea0c11e1e190ef6f43710fe"
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
],
"version": "==21.2.2"
"version": "==21.3.1"
},
"automat": {
"hashes": [
@ -116,18 +116,18 @@
},
"boto3": {
"hashes": [
"sha256:3570a3c0fbd80bcb30449f87cf9d2f7abb67fac2a5e317d002f9921c59be9b17",
"sha256:ceff2f32ba05acc9ee35a6dd82e29ea285d63e889bed39a6ba7a700146f43749"
"sha256:c9513a9ea00f8d17ecdc02c391ae956bf0f990aa07deec11c421607c09b294e1",
"sha256:f84ca60e9605af69022f039c035b33d519531eeaac52724b9223a5465f4a8b6b"
],
"index": "pypi",
"version": "==1.17.18"
"version": "==1.17.19"
},
"botocore": {
"hashes": [
"sha256:51900b10da4ae45be4b16045e5b2ff7d1158a7955d9d7cc5e5a9ba3170f10586",
"sha256:b181f32d9075e5419a89fa9636ce95946c15459c9bfadfabb53ca902fc8072b8"
"sha256:135b5f30e6662b46d804f993bf31d9c7769c6c0848321ed0aa0393f5b9c19a94",
"sha256:8e42c78d2eb888551635309158c04ef2648a96d8c2c70dbce7712c6ce8629759"
],
"version": "==1.20.18"
"version": "==1.20.19"
},
"cachetools": {
"hashes": [
@ -1249,10 +1249,10 @@
},
"websocket-client": {
"hashes": [
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
"sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
"sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663",
"sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"
],
"version": "==0.57.0"
"version": "==0.58.0"
},
"websockets": {
"hashes": [

View File

@ -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

View File

@ -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

View File

@ -1,2 +1,2 @@
"""authentik"""
__version__ = "2021.3.1-rc1"
__version__ = "2021.3.1"

View File

@ -7,14 +7,14 @@ from django.http.response import Http404
from django.utils.translation import gettext_lazy as _
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.events.monitored_tasks import TaskInfo
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
class TaskSerializer(Serializer):
@ -24,7 +24,10 @@ class TaskSerializer(Serializer):
task_description = CharField()
task_finish_timestamp = DateTimeField(source="finish_timestamp")
status = IntegerField(source="result.status.value")
status = ChoiceField(
source="result.status.name",
choices=[(x.name, x.name) for x in TaskResultStatus],
)
messages = ListField(source="result.messages")
def create(self, validated_data: dict) -> Model:

View File

@ -55,7 +55,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet):
def get_queryset(self): # pragma: no cover
return None
@swagger_auto_schema(responses={200: VersionSerializer(many=True)})
@swagger_auto_schema(responses={200: VersionSerializer(many=False)})
def list(self, request: Request) -> Response:
"""Get running and latest version."""
return Response(VersionSerializer(True).data)

View File

@ -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>&nbsp;
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
</footer>
{% endblock %}

View File

@ -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):

View File

@ -0,0 +1,97 @@
"""Swagger Pagination Schema class"""
from typing import OrderedDict
from drf_yasg2 import openapi
from drf_yasg2.inspectors import PaginatorInspector
class PaginationInspector(PaginatorInspector):
"""Swagger Pagination Schema class"""
def get_paginated_response(self, paginator, response_schema):
"""
:param BasePagination paginator: the paginator
:param openapi.Schema response_schema: the response schema that must be paged.
:rtype: openapi.Schema
"""
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
(
"pagination",
openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"previous",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"current",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"total_pages",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"start_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"end_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
)
),
required=[
"next",
"previous",
"count",
"current",
"total_pages",
"start_index",
"end_index",
],
),
),
("results", response_schema),
)
),
required=["results", "pagination"],
)
def get_paginator_parameters(self, paginator):
"""
Get the pagination parameters for a single paginator **instance**.
Should return :data:`.NotHandled` if this inspector
does not know how to handle the given `paginator`.
:param BasePagination paginator: the paginator
:rtype: list[openapi.Parameter]
"""
return [
openapi.Parameter(
"page",
openapi.IN_QUERY,
"Page Index",
False,
None,
openapi.TYPE_INTEGER,
),
openapi.Parameter(
"page_size",
openapi.IN_QUERY,
"Page Size",
False,
None,
openapi.TYPE_INTEGER,
),
]

View File

@ -1,10 +1,11 @@
"""core Configs API"""
from django.db.models import Model
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import BooleanField, CharField
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ReadOnlyField, Serializer
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.lib.config import CONFIG
@ -13,12 +14,12 @@ from authentik.lib.config import CONFIG
class ConfigSerializer(Serializer):
"""Serialize authentik Config into DRF Object"""
branding_logo = ReadOnlyField()
branding_title = ReadOnlyField()
branding_logo = CharField(read_only=True)
branding_title = CharField(read_only=True)
error_reporting_enabled = ReadOnlyField()
error_reporting_environment = ReadOnlyField()
error_reporting_send_pii = ReadOnlyField()
error_reporting_enabled = BooleanField(read_only=True)
error_reporting_environment = CharField(read_only=True)
error_reporting_send_pii = BooleanField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
@ -32,7 +33,7 @@ class ConfigsViewSet(ViewSet):
permission_classes = [AllowAny]
@swagger_auto_schema(responses={200: ConfigSerializer(many=True)})
@swagger_auto_schema(responses={200: ConfigSerializer(many=False)})
def list(self, request: Request) -> Response:
"""Retrive public configuration options"""
config = ConfigSerializer(

View File

@ -1,37 +0,0 @@
"""core messages API"""
from django.contrib.messages import get_messages
from django.db.models import Model
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ReadOnlyField, Serializer
from rest_framework.viewsets import ViewSet
class MessageSerializer(Serializer):
"""Serialize Django Message into DRF Object"""
message = ReadOnlyField()
level = ReadOnlyField()
tags = ReadOnlyField()
extra_tags = ReadOnlyField()
level_tag = ReadOnlyField()
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class MessagesViewSet(ViewSet):
"""Read-only view set that returns the current session's messages"""
permission_classes = [AllowAny]
@swagger_auto_schema(responses={200: MessageSerializer(many=True)})
def list(self, request: Request) -> Response:
"""List current messages and pass into Serializer"""
all_messages = list(get_messages(request))
return Response(MessageSerializer(all_messages, many=True).data)

View File

@ -10,7 +10,6 @@ from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionViewSet
from authentik.admin.api.workers import WorkerViewSet
from authentik.api.v2.config import ConfigsViewSet
from authentik.api.v2.messages import MessagesViewSet
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.groups import GroupViewSet
from authentik.core.api.propertymappings import PropertyMappingViewSet
@ -77,7 +76,6 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
router = routers.DefaultRouter()
router.register("root/messages", MessagesViewSet, basename="messages")
router.register("root/config", ConfigsViewSet, basename="configs")
router.register("admin/version", VersionViewSet, basename="admin_version")

View File

@ -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."))

View File

@ -46,8 +46,7 @@ def backup_database(self: MonitoredTask): # pragma: no cover
TaskResult(
TaskResultStatus.SUCCESSFUL,
[
f"Successfully finished database backup {naturaltime(start)}",
out.getvalue(),
f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}",
],
)
)

View File

@ -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 %}

View File

@ -1,4 +1,4 @@
{% extends 'base/page.html' %}
{% extends 'base/skeleton.html' %}
{% load i18n %}
{% load authentik_utils %}

View File

@ -13,7 +13,7 @@
<p>{% trans "Configure settings relevant to your user profile." %}</p>
</div>
</section>
<ak-tabs>
<ak-tabs vertical="true">
<section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">

View File

@ -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:

View File

@ -29,7 +29,7 @@ class EventSerializer(ModelSerializer):
]
class EventTopPerUserSerialier(Serializer):
class EventTopPerUserSerializer(Serializer):
"""Response object of Event's top_per_user"""
application = DictField()
@ -60,7 +60,7 @@ class EventViewSet(ReadOnlyModelViewSet):
filterset_fields = ["action"]
@swagger_auto_schema(
method="GET", responses={200: EventTopPerUserSerialier(many=True)}
method="GET", responses={200: EventTopPerUserSerializer(many=True)}
)
@action(detail=False, methods=["GET"])
def top_per_user(self, request: Request):

View File

@ -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

View File

@ -5,6 +5,7 @@ from typing import Type
from kubernetes.client import OpenApiException
from kubernetes.client.api_client import ApiClient
from structlog.testing import capture_logs
from urllib3.exceptions import HTTPError
from yaml import dump_all
from authentik.outposts.controllers.base import BaseController, ControllerException
@ -42,7 +43,7 @@ class KubernetesController(BaseController):
reconciler = self.reconcilers[reconcile_key](self)
reconciler.up()
except OpenApiException as exc:
except (OpenApiException, HTTPError) as exc:
raise ControllerException from exc
def up_with_logs(self) -> list[str]:
@ -54,7 +55,7 @@ class KubernetesController(BaseController):
reconciler.up()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
return all_logs
except OpenApiException as exc:
except (OpenApiException, HTTPError) as exc:
raise ControllerException from exc
def down(self):

View File

@ -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.",
),
),
]

View File

@ -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()

View File

@ -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)

View File

@ -139,6 +139,9 @@ GUARDIAN_MONKEY_PATCH = False
SWAGGER_SETTINGS = {
"DEFAULT_INFO": "authentik.api.v2.urls.info",
"DEFAULT_PAGINATOR_INSPECTORS": [
"authentik.api.pagination_schema.PaginationInspector",
],
"SECURITY_DEFINITIONS": {
"token": {"type": "apiKey", "name": "Authorization", "in": "header"}
},
@ -147,7 +150,6 @@ SWAGGER_SETTINGS = {
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
"PAGE_SIZE": 100,
"DATETIME_FORMAT": "%s",
"DEFAULT_FILTER_BACKENDS": [
"rest_framework_guardian.filters.ObjectPermissionsFilter",
"django_filters.rest_framework.DjangoFilterBackend",

View File

@ -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

View File

@ -1,9 +1,6 @@
"""OTP Validate stage forms"""
from django import forms
from django.utils.translation import gettext_lazy as _
from django_otp import match_token
from authentik.core.models import User
from authentik.flows.models import NotConfiguredAction
from authentik.stages.authenticator_validate.models import (
AuthenticatorValidateStage,
@ -11,35 +8,6 @@ from authentik.stages.authenticator_validate.models import (
)
class ValidationForm(forms.Form):
"""OTP Validate stage forms"""
user: User
code = forms.CharField(
label=_("Please enter the token from your device."),
widget=forms.TextInput(
attrs={
"autocomplete": "one-time-code",
"placeholder": "123456",
"autofocus": "autofocus",
}
),
)
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def clean_code(self):
"""Validate code against all confirmed devices"""
code = self.cleaned_data.get("code")
device = match_token(self.user, code)
if not device:
raise forms.ValidationError(_("Invalid Token"))
return code
class AuthenticatorValidateStageForm(forms.ModelForm):
"""OTP Validate stage forms"""

View File

@ -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(),
}

View File

@ -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")

View File

@ -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>

View File

@ -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"),
]

View File

@ -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

View File

@ -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 %}

View File

@ -378,8 +378,8 @@ stages:
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'GHCR'
repository: 'beryju/authentik'
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/server'
command: 'buildAndPush'
Dockerfile: 'Dockerfile'
tags: "gh-$(branchName)"

View File

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

View File

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

View File

@ -4,7 +4,7 @@
|-----------------------------------|-------------------------|-------------|
| 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.tag | 2021.3.1-rc1 | Image tag |
| image.tag | 2021.3.1 | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment |

View File

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

View File

@ -98,8 +98,8 @@ stages:
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'GHCR'
repository: 'beryju/authentik-proxy'
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/proxy'
command: 'buildAndPush'
Dockerfile: 'outpost/proxy.Dockerfile'
buildContext: 'outpost/'

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -78,8 +78,8 @@ stages:
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'GHCR'
repository: 'beryju/authentik-static'
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/static'
command: 'buildAndPush'
Dockerfile: 'web/Dockerfile'
tags: "gh-$(branchName)"

View File

@ -1,10 +1,14 @@
import { gettext } from "django";
import { showMessage } from "../elements/messages/MessageContainer";
import { getCookie } from "../utils";
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 {
@ -45,6 +49,13 @@ export class Client {
}
return r;
})
.catch((e) => {
showMessage({
level_tag: "error",
message: gettext(`Unexpected error while fetching: ${e.toString()}`),
});
return e;
})
.then((r) => r.json())
.then((r) => <T>r);
}

View File

@ -8,6 +8,7 @@ export interface OAuth2SetupURLs {
token: string;
user_info: string;
provider_info?: string;
logout?: string;
}

View File

@ -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;

View File

@ -3,4 +3,5 @@ export const SUCCESS_CLASS = "pf-m-success";
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 VERSION = "2021.3.1";
export const PAGE_SIZE = 20;

View File

@ -1,16 +1,21 @@
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import Chart from "chart.js";
import { showMessage } from "./messages/MessageContainer";
import { DefaultClient } from "../api/Client";
interface TickValue {
value: number;
major: boolean;
}
export interface LoginMetrics {
logins_failed_per_1h: { x: number, y: number }[];
logins_per_1h: { x: number, y: number }[];
}
@customElement("ak-admin-logins-chart")
export class AdminLoginsChart extends LitElement {
@property()
url = "";
@property({type: Array})
url: string[] = [];
chart?: Chart;
@ -40,15 +45,7 @@ export class AdminLoginsChart extends LitElement {
}
firstUpdated(): void {
fetch(this.url)
.then((r) => r.json())
.catch((e) => {
showMessage({
level_tag: "error",
message: "Unexpected error"
});
console.error(e);
})
DefaultClient.fetch<LoginMetrics>(this.url)
.then((r) => {
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
if (!canvas) {

View File

@ -12,10 +12,17 @@ export class Tabs extends LitElement {
@property()
currentPage?: string;
@property({type: Boolean})
vertical = false;
static get styles(): CSSResult[] {
return [GlobalsStyle, TabsStyle, css`
::slotted(*) {
height: 100%;
flex-grow: 2;
}
:host([vertical]) {
display: flex;
}
`];
}
@ -39,7 +46,7 @@ export class Tabs extends LitElement {
}
this.currentPage = pages[0].attributes.getNamedItem("slot")?.value;
}
return html`<div class="pf-c-tabs">
return html`<div class="pf-c-tabs ${this.vertical ? "pf-m-vertical pf-m-box" : ""}">
<ul class="pf-c-tabs__list">
${pages.map((page) => this.renderTab(page))}
</ul>

View File

@ -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();
}

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { LitElement, html, customElement, TemplateResult, property } from "lit-element";
import { DefaultClient } from "../../api/Client";
import "./Message";
import { APIMessage } from "./Message";
@ -15,7 +14,6 @@ export function showMessage(message: APIMessage): void {
@customElement("ak-message-container")
export class MessageContainer extends LitElement {
url = DefaultClient.makeUrl(["root", "messages"]);
@property({attribute: false})
messages: APIMessage[] = [];
@ -36,10 +34,6 @@ export class MessageContainer extends LitElement {
}
}
firstUpdated(): void {
this.fetchMessages();
}
connect(): void {
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
window.location.host
@ -74,21 +68,6 @@ export class MessageContainer extends LitElement {
});
}
/* Fetch messages which were stored in the session.
* This mostly gets messages which were created when the user arrives/leaves the site
* and especially the login flow */
fetchMessages(): Promise<void> {
console.debug("authentik/messages: fetching messages over direct api");
return fetch(this.url)
.then((r) => r.json())
.then((r: APIMessage[]) => {
r.forEach((m: APIMessage) => {
this.messages.push(m);
this.requestUpdate();
});
});
}
render(): TemplateResult {
return html`<ul class="pf-c-alert-group pf-m-toast">
${this.messages.map((m) => {

View File

@ -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>`;
}

View File

@ -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,
});
}

View File

@ -119,13 +119,15 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
return html`<ak-stage-authenticator-validate-code
.host=${this}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}>
.deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
</ak-stage-authenticator-validate-code>`;
case DeviceClasses.WEBAUTHN:
return html`<ak-stage-authenticator-validate-webauthn
.host=${this}
.challenge=${this.challenge}
.deviceChallenge=${this.selectedDeviceChallenge}>
.deviceChallenge=${this.selectedDeviceChallenge}
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
</ak-stage-authenticator-validate-webauthn>`;
}
}

View File

@ -14,6 +14,9 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
@property({ attribute: false })
deviceChallenge?: DeviceChallenge;
@property({ type: Boolean })
showBackButton = false;
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
@ -61,14 +64,16 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links">
<li class="pf-c-login__main-footer-links-item">
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
if (!this.host) return;
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
}}>
${gettext("Return to device picker")}
</button>
</li>
${this.showBackButton ?
html`<li class="pf-c-login__main-footer-links-item">
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
if (!this.host) return;
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
}}>
${gettext("Return to device picker")}
</button>
</li>`:
html``}
</ul>
</footer>`;
}

View File

@ -21,6 +21,9 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
@property()
authenticateMessage = "";
@property({type: Boolean})
showBackButton = false;
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
@ -98,14 +101,16 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links">
<li class="pf-c-login__main-footer-links-item">
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
if (!this.host) return;
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
}}>
${gettext("Return to device picker")}
</button>
</li>
${this.showBackButton ?
html`<li class="pf-c-login__main-footer-links-item">
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
if (!this.host) return;
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
}}>
${gettext("Return to device picker")}
</button>
</li>`:
html``}
</ul>
</footer>`;
}

View File

@ -1,6 +1,5 @@
import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
import { DefaultClient } from "../../api/Client";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/AdminLoginsChart";
@ -31,7 +30,7 @@ export class AdminOverviewPage extends LitElement {
<section class="pf-c-page__main-section">
<div class="pf-l-gallery pf-m-gutter">
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
<ak-admin-logins-chart url="${DefaultClient.makeUrl(["admin", "metrics"])}"></ak-admin-logins-chart>
<ak-admin-logins-chart .url="${["admin", "metrics"]}"></ak-admin-logins-chart>
</ak-aggregate-card>
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
<ak-top-applications-table></ak-top-applications-table>

View File

@ -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 || "",
});
}

View File

@ -1,7 +1,6 @@
import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Application } from "../../api/Applications";
import { DefaultClient } from "../../api/Client";
import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Tabs";
@ -71,7 +70,7 @@ export class ApplicationViewPage extends LitElement {
<div class="pf-c-card__body">
${this.application ? html`
<ak-admin-logins-chart
url="${DefaultClient.makeUrl(["core", "applications", this.application?.slug, "metrics"])}">
.url="${["core", "applications", this.application?.slug, "metrics"]}">
</ak-admin-logins-chart>`: ""}
</div>
</div>

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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,
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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,
});

View File

@ -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>

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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 || "",
});
}

View File

@ -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}`,

View File

@ -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 || "",
});
}

View File

@ -12,8 +12,8 @@ These functions/objects are available wherever expressions are used. For more sp
## Global objects
- `ak_logger`: structlog BoundLogger. ([ref](https://www.structlog.org/en/stable/api.html#structlog.BoundLogger))
- `requests`: requests Session object. ([ref](https://requests.readthedocs.io/en/master/user/advanced/))
- `ak_logger`: structlog BoundLogger. ([ref](https://www.structlog.org/en/stable/api.html#structlog.BoundLogger))
- `requests`: requests Session object. ([ref](https://requests.readthedocs.io/en/master/user/advanced/))
## Generally available functions

View File

@ -4,16 +4,16 @@ title: User Object
The User object has the following attributes:
- `username`: User's username.
- `email` User's email.
- `name` User's display name.
- `is_staff` Boolean field if user is staff.
- `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes.
- `group_attributes` Merged attributes of all groups the user is member of and the user's own attributes.
- `ak_groups` This is a queryset of all the user's groups.
- `username`: User's username.
- `email` User's email.
- `name` User's display name.
- `is_staff` Boolean field if user is staff.
- `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes.
- `group_attributes` Merged attributes of all groups the user is member of and the user's own attributes.
- `ak_groups` This is a queryset of all the user's groups.
You can do additional filtering like `user.ak_groups.filter(name__startswith='test')`, see [here](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#id4)

View File

@ -1,5 +1,5 @@
---
title: Static Authenticator stage
title: Static Authentication Setup stage
---
This stage configures static OTP Tokens, which can be used as a backup method to time-based OTP tokens.

View File

@ -1,7 +1,7 @@
---
title: TOTP stage
title: TOTP Authentication Setup stage
---
This stage configures a time-based OTP Device, such as Google Authenticator or Authy.
You can configure how many digest should be used for the OTP Token.
You can configure how many digits should be used for the OTP Token.

View File

@ -2,7 +2,16 @@
title: Authenticator Validation Stage
---
This stage validates an already configured OTP Device. This device has to be configured using any of the other authenticator stages:
This stage validates an already configured Authenticator Device. This device has to be configured using any of the other authenticator stages:
- [TOTP authenticator stage](../authenticator_totp/index.md)
- [Static authenticator stage](../authenticator_static/index.md).
- [WebAuth authenticator stage](../authenticator_webauthn/index.md).
You can select which type of device classes are allowed.
Using the `Not configured action`, you can choose what happens when a user does not have any matching devices.
- Skip: Validation is skipped and the flow continues
- Deny: Access is denied, the flow execution ends
- Configure: This option requires a *Configuration stage* to be set. The validation stage will be marked as successful, and the configuration stage will be injected into the flow.

View File

@ -0,0 +1,7 @@
---
title: WebAuthn Authentication Setup stage
---
This stage configures a WebAuthn-based Authenticator. This can either be a browser, biometrics or a Security stick like a YubiKey.
There are no stage-specific settings.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -0,0 +1,10 @@
---
title: Deny stage
---
This stage stops the execution of a flow. This can be used to conditionally deny users access to a flow,
even if they are not signed in (and permissions can't be checked via groups).
:::caution
To effectively use this stage, make sure to **disable** *Evaluate on plan* on the Stage binding.
:::

View File

@ -11,8 +11,8 @@ This stage provides a ready-to-go form for users to identify themselves.
Select which fields the user can use to identify themselves. Multiple fields can be specified and separated with a comma.
Valid choices:
- email
- username
- email
- username
### Template

View File

@ -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)

View File

@ -16,7 +16,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 deploy a different version run `echo AUTHENTIK_TAG=2021.3.1-rc1 >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.3.1 >> .env`
If this is a fresh authentik install run the following commands to generate a password:

View File

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

View File

@ -0,0 +1,62 @@
---
title: Apache Guacamole™
---
## What is Apache Guacamole™
From https://guacamole.apache.org/
:::note
Apache Guacamole is a clientless remote desktop gateway. It supports standard protocols like VNC, RDP, and SSH.
:::
## Preparation
The following placeholders will be used:
- `guacamole.company` is the FQDN of the Guacamole install.
- `authentik.company` is the FQDN of the authentik install.
Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Redirect URIs: `https://guacamole.company/` (depending on your Tomcat setup, you might have to add `/guacamole/` if the application runs in a subfolder)
- Scopes: OpenID, Email and Profile
Note the Client ID value. Create an application, using the provider you've created above.
## Guacamole
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
defaultValue="docker"
values={[
{label: 'Docker', value: 'docker'},
{label: 'Standalone', value: 'standalone'},
]}>
<TabItem value="docker">
The docker containers are configured via environment variables. The following variables are required:
```yaml
OPENID_AUTHORIZATION_ENDPOINT: https://authentik.company/application/o/authorize/
OPENID_CLIENT_ID: # client ID from above
OPENID_ISSUER: https://authentik.company/application/o/apache-guacamole/
OPENID_JWKS_ENDPOINT: https://authentik.company/application/o/apache-guacamole/jwks/
OPENID_REDIRECT_URI: https://guacamole.company/ # This must match the redirect URI above
```
</TabItem>
<TabItem value="standalone">
Standalone Guacamole is configured using the `guacamole.properties` file. Add the following settings:
```
openid-authorization-endpoint=https://authentik.company/application/o/authorize/
openid-client-id=# client ID from above
openid-issuer=https://authentik.company/application/o/apache-guacamole/
openid-jwks-endpoint=https://authentik.company/application/o/apache-guacamole/jwks/
openid-redirect-uri=https://guacamole.company/ # This must match the redirect URI above
```
</TabItem>
</Tabs>

View File

@ -12,14 +12,14 @@ Amazon Web Services (AWS) is the worlds most comprehensive and broadly adopte
The following placeholders will be used:
- `authentik.company` is the FQDN of the authentik install.
- `authentik.company` is the FQDN of the authentik install.
Create an application in authentik and note the slug, as this will be used later. Create a SAML provider with the following parameters:
- ACS URL: `https://signin.aws.amazon.com/saml`
- Audience: `urn:amazon:webservices`
- Issuer: `authentik`
- Binding: `Post`
- ACS URL: `https://signin.aws.amazon.com/saml`
- Audience: `urn:amazon:webservices`
- Issuer: `authentik`
- Binding: `Post`
You can of course use a custom signing certificate, and adjust durations.

View File

@ -20,15 +20,15 @@ AWX is the open-source version of Tower. The term "AWX" will be used interchange
The following placeholders will be used:
- `awx.company` is the FQDN of the AWX/Tower install.
- `authentik.company` is the FQDN of the authentik install.
- `awx.company` is the FQDN of the AWX/Tower install.
- `authentik.company` is the FQDN of the authentik install.
Create an application in authentik and note the slug, as this will be used later. Create a SAML provider with the following parameters:
- ACS URL: `https://awx.company/sso/complete/saml/`
- Audience: `awx`
- Service Provider Binding: Post
- Issuer: `https://awx.company/sso/metadata/saml/`
- ACS URL: `https://awx.company/sso/complete/saml/`
- Audience: `awx`
- Service Provider Binding: Post
- Issuer: `https://awx.company/sso/metadata/saml/`
You can of course use a custom signing certificate, and adjust durations.

View File

@ -14,15 +14,15 @@ GitLab is a complete DevOps platform, delivered as a single application. This ma
The following placeholders will be used:
- `gitlab.company` is the FQDN of the GitLab Install
- `authentik.company` is the FQDN of the authentik Install
- `gitlab.company` is the FQDN of the GitLab Install
- `authentik.company` is the FQDN of the authentik Install
Create an application in authentik and note the slug, as this will be used later. Create a SAML provider with the following parameters:
- ACS URL: `https://gitlab.company/users/auth/saml/callback`
- Audience: `https://gitlab.company`
- Issuer: `https://gitlab.company`
- Binding: `Post`
- ACS URL: `https://gitlab.company/users/auth/saml/callback`
- Audience: `https://gitlab.company`
- Issuer: `https://gitlab.company`
- Binding: `Post`
You can of course use a custom signing certificate, and adjust durations. To get the value for `idp_cert_fingerprint`, you can use a tool like [this](https://www.samltool.com/fingerprint.php).

View File

@ -14,21 +14,31 @@ Grafana is a multi-platform open source analytics and interactive visualization
The following placeholders will be used:
- `grafana.company` is the FQDN of the Grafana install.
- `authentik.company` is the FQDN of the authentik install.
- `grafana.company` is the FQDN of the Grafana install.
- `authentik.company` is the FQDN of the authentik install.
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile
- RSA Key: Select any available key
- Redirect URIs: `https://grafana.company/login/generic_oauth`
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile
- RSA Key: Select any available key
- Redirect URIs: `https://grafana.company/login/generic_oauth`
Note the Client ID and Client Secret values. Create an application, using the provider you've created above. Note the slug of the application you've created.
## Grafana
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
defaultValue="docker"
values={[
{label: 'Docker', value: 'docker'},
{label: 'Standalone', value: 'standalone'},
]}>
<TabItem value="docker">
If your Grafana is running in docker, set the following environment variables:
```yaml
@ -45,7 +55,8 @@ environment:
# Optionally enable auto-login
GF_AUTH_OAUTH_AUTO_LOGIN: "true"
```
</TabItem>
<TabItem value="standalone">
If you are using a config-file instead, you have to set these options:
```ini
@ -64,3 +75,5 @@ auth_url = https://authentik.company/application/o/authorize/
token_url = https://authentik.company/application/o/token/
api_url = https://authentik.company/application/o/userinfo/
```
</TabItem>
</Tabs>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -14,15 +14,15 @@ Harbor is an open source container image registry that secures images with role-
The following placeholders will be used:
- `harbor.company` is the FQDN of the Harbor install.
- `authentik.company` is the FQDN of the authentik install.
- `harbor.company` is the FQDN of the Harbor install.
- `authentik.company` is the FQDN of the authentik install.
Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Redirect URIs: `https://harbor.company/c/oidc/callback`
- Scopes: OpenID, Email and Profile
- Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Redirect URIs: `https://harbor.company/c/oidc/callback`
- Scopes: OpenID, Email and Profile
Note the Client ID and Client Secret values. Create an application, using the provider you've created above.

View File

@ -14,8 +14,8 @@ Open source home automation that puts local control and privacy first. Powered b
The following placeholders will be used:
- `hass.company` is the FQDN of the Home-Assistant install.
- `authentik.company` is the FQDN of the authentik install.
- `hass.company` is the FQDN of the Home-Assistant install.
- `authentik.company` is the FQDN of the authentik install.
:::note
This setup uses https://github.com/BeryJu/hass-auth-header and the authentik proxy for authentication. When this [PR](https://github.com/home-assistant/core/pull/32926) is merged, this will no longer be necessary.
@ -42,13 +42,13 @@ additionalHeaders:
Create a Proxy Provider with the following values
- Internal host
- Internal host
If Home-Assistant is running in docker, and you're deploying the authentik proxy on the same host, set the value to `http://homeassistant:8123`, where Home-Assistant is the name of your container.
If Home-Assistant is running on a different server than where you are deploying the authentik proxy, set the value to `http://hass.company:8123`.
- External host
- External host
Set this to the external URL you will be accessing Home-Assistant from.

Some files were not shown because too many files have changed in this diff Show More