sources/oauth: migrate to webcomponents

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-24 20:19:10 +01:00
parent a085632b8e
commit 533a719914
24 changed files with 286 additions and 522 deletions

View File

@ -1,42 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
{% block above_form %}
<h1>
{% blocktrans with object_type=object|verbose_name %}
Disable {{ object_type }}
{% endblocktrans %}
</h1>
{% endblock %}
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-l-stack">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form action="" method="post" class="pf-c-form">
{% csrf_token %}
<p>
{% blocktrans with object_type=object|verbose_name name=object %}
Are you sure you want to disable {{ object_type }} "{{ object }}"?
{% endblocktrans %}
</p>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -30,7 +30,7 @@
<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>
<a class="pf-c-button pf-m-secondary" href="#/">{% trans "Cancel" %}</a>
</footer>
{% endblock %}

View File

@ -2,6 +2,10 @@
{% load static %}
{% block title %}
authentik API Browser
{% endblock %}
{% block head %}
<script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script>
{% endblock %}

View File

@ -107,7 +107,6 @@ router.register("core/applications", ApplicationViewSet)
router.register("core/groups", GroupViewSet)
router.register("core/users", UserViewSet)
router.register("core/user_consent", UserConsentViewSet)
router.register("core/source_user_connections_oauth", UserOAuthSourceConnectionViewSet)
router.register("core/tokens", TokenViewSet)
router.register("outposts/outposts", OutpostViewSet)
@ -129,6 +128,7 @@ router.register("events/transports", NotificationTransportViewSet)
router.register("events/rules", NotificationRuleViewSet)
router.register("sources/all", SourceViewSet)
router.register("sources/oauth_user_connections", UserOAuthSourceConnectionViewSet)
router.register("sources/ldap", LDAPSourceViewSet)
router.register("sources/saml", SAMLSourceViewSet)
router.register("sources/oauth", OAuthSourceViewSet)

View File

@ -94,7 +94,7 @@ class SourceViewSet(
if not policy_engine.passing:
continue
source_settings = source.ui_user_settings
source_settings.initial_data["object_uid"] = str(source.pk)
source_settings.initial_data["object_uid"] = source.slug
if not source_settings.is_valid():
LOGGER.warning(source_settings.errors)
matching_sources.append(source_settings.validated_data)

View File

@ -25,9 +25,6 @@
<h3>{% trans message %}</h3>
{% endif %}
</div>
{% if 'back' in request.GET %}
<a href="{% back %}" class="pf-c-button pf-m-primary pf-m-block">{% trans 'Back' %}</a>
{% endif %}
<a href="/" class="pf-c-button pf-m-primary pf-m-block">{% trans 'Go to home' %}</a>
</div>
</div>

View File

@ -1,37 +0,0 @@
{% load i18n %}
{% load authentik_utils %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
{% block above_form %}
<h1>
{% blocktrans with object_type=object|verbose_name %}
Delete {{ object_type }}
{% endblocktrans %}
</h1>
{% endblock %}
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-l-stack">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form id="delete-form" action="" method="post" class="pf-c-form">
{% csrf_token %}
<p>
{% blocktrans with object_type=object|verbose_name name=object %}
Are you sure you want to delete {{ object_type }} "{{ object }}"?
{% endblocktrans %}
</p>
<input type="hidden" name="confirmdelete" value="yes">
</form>
</div>
</div>
</div>
</div>
</section>
<footer class="pf-c-modal-box__footer">
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
</footer>

View File

@ -1,26 +0,0 @@
{% load static %}
{% load i18n %}
<ak-message-container></ak-message-container>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% trans title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
<form method="POST" class="pf-c-form">
{% include 'partials/form.html' %}
<div class="pf-c-form__group pf-m-action">
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">Log in</button>
</div>
</form>
{% endblock %}
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links">
</ul>
</footer>

View File

@ -1,73 +0,0 @@
{% load authentik_utils %}
{% load i18n %}
{% csrf_token %}
{% if form.non_field_errors %}
<div class="pf-c-form__group">
<p class="pf-c-form__helper-text pf-m-error">
{{ form.non_field_errors }}
</p>
</div>
{% endif %}
{% for field in form %}
{% if field.field.widget|fieldtype == 'HiddenInput' %}
{{ field }}
{% else %}
<div class="pf-c-form__group">
{% if field.field.widget|fieldtype == 'RadioSelect' %}
<label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %}
for="{{ field.name }}-{{ forloop.counter0 }}">
{{ field.label }}
</label>
{% for c in field %}
<div class="radio col-sm-10">
<input type="radio" id="{{ field.name }}-{{ forloop.counter0 }}"
name="{% if wizard %}{{ wizard.steps.current }}-{% endif %}{{ field.name }}" value="{{ c.data.value }}"
{% if c.data.selected %} checked {% endif %}>
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">{{ c.choice_label }}</label>
</div>
{% endfor %}
{% elif field.field.widget|fieldtype == 'Select' %}
<label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %}
for="{{ field.name }}-{{ forloop.counter0 }}">
{{ field.label }}
</label>
<div class="select col-sm-10">
{{ field }}
</div>
{% if field.help_text %}
<span>
{{ field.help_text }}
</span>
{% endif %}
{% elif field.field.widget|fieldtype == 'CheckboxInput' %}
<label class="checkbox-label">
{{ field }} {{ field.label }}
</label>
{% if field.help_text %}
<span>
{{ field.help_text }}
</span>
{% endif %}
{% else %}
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
{{ field|css_class:'pf-c-form-control' }}
{% if field.help_text %}
<span>
{{ field.help_text }}
</span>
{% endif %}
{% endif %}
{% for error in field.errors %}
<p class="pf-c-form__helper-text pf-m-error">
{{ error }}
</p>
{% endfor %}
</div>
{% endif %}
{% endfor %}

View File

@ -87,7 +87,7 @@ class StageViewSet(
user_settings = stage.ui_user_settings
if not user_settings:
continue
user_settings.initial_data["object_uid"] = stage.pk
user_settings.initial_data["object_uid"] = str(stage.pk)
if not user_settings.is_valid():
LOGGER.warning(user_settings.errors)
matching_stages.append(user_settings.initial_data)

View File

@ -2,32 +2,12 @@
from django import template
from django.db.models import Model
from django.template import Context
from structlog.stdlib import get_logger
from authentik.lib.utils.urls import is_url_absolute
register = template.Library()
LOGGER = get_logger()
@register.simple_tag(takes_context=True)
def back(context: Context) -> str:
"""Return a link back (either from GET parameter or referer."""
if "request" not in context:
return ""
request = context.get("request")
url = ""
if "HTTP_REFERER" in request.META:
url = request.META.get("HTTP_REFERER")
if "back" in request.GET:
url = request.GET.get("back")
if not is_url_absolute(url):
return url
return ""
@register.filter("fieldtype")
def fieldtype(field):
"""Return classname"""

View File

@ -15,7 +15,6 @@
{% block card %}
<form method="POST" class="pf-c-form">
{% csrf_token %}
{% include 'partials/form.html' %}
<div class="pf-c-form__group">
<p>
<i class="pf-icon pf-icon-error-circle-o"></i>
@ -37,29 +36,26 @@
</li>
{% endfor %}
</ul>
{% if policy_result.source_results %}
<em>{% trans 'Explanation:' %}</em>
<ul class="pf-c-list">
{% for source_result in policy_result.source_results %}
<li>
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
Policy '{{ name }}' returned result '{{ result }}'
{% endblocktrans %}
{% if source_result.messages %}
<ul class="pf-c-list">
{% for message in source_result.messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if policy_result.source_results %}
<em>{% trans 'Explanation:' %}</em>
<ul class="pf-c-list">
{% for source_result in policy_result.source_results %}
<li>
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
Policy '{{ name }}' returned result '{{ result }}'
{% endblocktrans %}
{% if source_result.messages %}
<ul class="pf-c-list">
{% for message in source_result.messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</div>
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View File

@ -11,10 +11,10 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer):
class Meta:
model = UserOAuthSourceConnection
fields = [
"pk",
"user",
"source",
"identifier",
"access_token",
]
@ -23,6 +23,7 @@ class UserOAuthSourceConnectionViewSet(ModelViewSet):
queryset = UserOAuthSourceConnection.objects.all()
serializer_class = UserOAuthSourceConnectionSerializer
filterset_fields = ["source"]
def get_queryset(self):
if not self.request:

View File

@ -9,8 +9,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import Source, UserSourceConnection
from authentik.core.types import UILoginButton
from authentik.flows.challenge import Challenge, ChallengeTypes
from authentik.core.types import UILoginButton, UserSettingSerializer
class OAuthSource(Source):
@ -67,13 +66,11 @@ class OAuthSource(Source):
)
@property
def ui_user_settings(self) -> Optional[Challenge]:
view_name = "authentik_sources_oauth:oauth-client-user"
return Challenge(
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer(
data={
"type": ChallengeTypes.shell.value,
"title": self.name,
"component": reverse(view_name, kwargs={"source_slug": self.slug}),
"title": f"OAuth {self.name}",
"component": "ak-user-settings-source-oauth",
}
)

View File

@ -1,24 +0,0 @@
{% load i18n %}
<div class="pf-c-card">
<div class="pf-pf-c-card__title">
{% blocktrans with source_name=source.name %}
Source {{ source_name }}
{% endblocktrans %}
</div>
<div class="pf-c-card__body">
{% if connections.exists %}
<p>{% trans 'Connected.' %}</p>
<a class="pf-c-button pf-m-danger ak-root-link"
href="{% url 'authentik_sources_oauth:oauth-client-disconnect' source_slug=source.slug %}">
{% trans 'Disconnect' %}
</a>
{% else %}
<p>Not connected.</p>
<a class="pf-c-button pf-m-primary ak-root-link"
href="{% url 'authentik_sources_oauth:oauth-client-login' source_slug=source.slug %}">
{% trans 'Connect' %}
</a>
{% endif %}
</div>
</div>

View File

@ -4,7 +4,6 @@ from django.urls import path
from authentik.sources.oauth.types.manager import RequestKind
from authentik.sources.oauth.views.dispatcher import DispatcherView
from authentik.sources.oauth.views.user import DisconnectView, UserSettingsView
urlpatterns = [
path(
@ -17,14 +16,4 @@ urlpatterns = [
DispatcherView.as_view(kind=RequestKind.callback),
name="oauth-client-callback",
),
path(
"user/<slug:source_slug>/",
UserSettingsView.as_view(),
name="oauth-client-user",
),
path(
"user/<slug:source_slug>/disconnect/",
DisconnectView.as_view(),
name="oauth-client-disconnect",
),
]

View File

@ -1,70 +0,0 @@
"""authentik oauth_client user views"""
from typing import Optional
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic import TemplateView, View
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
class UserSettingsView(LoginRequiredMixin, TemplateView):
"""Show user current connection state"""
template_name = "oauth_client/user.html"
def get_context_data(self, **kwargs):
source = get_object_or_404(OAuthSource, slug=self.kwargs.get("source_slug"))
connections = UserOAuthSourceConnection.objects.filter(
user=self.request.user, source=source
)
kwargs["source"] = source
kwargs["connections"] = connections
return super().get_context_data(**kwargs)
class DisconnectView(LoginRequiredMixin, View):
"""Delete connection with source"""
source: Optional[OAuthSource] = None
aas: Optional[UserOAuthSourceConnection] = None
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
self.source = get_object_or_404(OAuthSource, slug=source_slug)
self.aas = get_object_or_404(
UserOAuthSourceConnection, source=self.source, user=request.user
)
return super().dispatch(request, source_slug)
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
"""Delete connection object"""
if "confirmdelete" in request.POST:
# User confirmed deletion
self.aas.delete()
messages.success(request, _("Connection successfully deleted"))
return redirect(
reverse(
"authentik_sources_oauth:oauth-client-user",
kwargs={"source_slug": self.source.slug},
)
)
return self.get(request, source_slug)
# pylint: disable=unused-argument
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
"""Show delete form"""
return render(
request,
"generic/delete.html",
{
"object": self.source,
"delete_url": reverse(
"authentik_sources_oauth:oauth-client-disconnect",
kwargs={"source_slug": self.source.slug},
),
},
)

View File

@ -9,6 +9,7 @@ from authentik.events.models import Event
@receiver(pre_delete, sender=StaticDevice)
# pylint: disable=unused-argument
def pre_delete_event(sender, instance: StaticDevice, **_):
"""Create event before deleting Static Devices"""
# Create event with email notification
event = Event.new(
"static_authenticator_disable", message="User disabled Static OTP Tokens."

View File

@ -9,6 +9,7 @@ from authentik.events.models import Event
@receiver(pre_delete, sender=TOTPDevice)
# pylint: disable=unused-argument
def pre_delete_event(sender, instance: TOTPDevice, **_):
"""Create event before deleting TOTP Devices"""
# Create event with email notification
event = Event.new("totp_disable", message="User disabled Time-based OTP.")
event.set_user(instance.user)

View File

@ -1188,147 +1188,6 @@ paths:
required: true
type: string
format: uuid
/core/source_user_connections_oauth/:
get:
operationId: core_source_user_connections_oauth_list
description: Source Viewset
parameters:
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: Page Index
required: false
type: integer
- name: page_size
in: query
description: Page Size
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- results
- pagination
type: object
properties:
pagination:
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
results:
type: array
items:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- core
post:
operationId: core_source_user_connections_oauth_create
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- core
parameters: []
/core/source_user_connections_oauth/{id}/:
get:
operationId: core_source_user_connections_oauth_read
description: Source Viewset
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- core
put:
operationId: core_source_user_connections_oauth_update
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- core
patch:
operationId: core_source_user_connections_oauth_partial_update
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- core
delete:
operationId: core_source_user_connections_oauth_delete
description: Source Viewset
parameters: []
responses:
'204':
description: ''
tags:
- core
parameters:
- name: id
in: path
description: A unique integer value identifying this User OAuth Source Connection.
required: true
type: integer
/core/tokens/:
get:
operationId: core_tokens_list
@ -7551,6 +7410,152 @@ paths:
type: string
format: slug
pattern: ^[-a-zA-Z0-9_]+$
/sources/oauth_user_connections/:
get:
operationId: sources_oauth_user_connections_list
description: Source Viewset
parameters:
- name: source
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: Page Index
required: false
type: integer
- name: page_size
in: query
description: Page Size
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- results
- pagination
type: object
properties:
pagination:
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
results:
type: array
items:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- sources
post:
operationId: sources_oauth_user_connections_create
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- sources
parameters: []
/sources/oauth_user_connections/{id}/:
get:
operationId: sources_oauth_user_connections_read
description: Source Viewset
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- sources
put:
operationId: sources_oauth_user_connections_update
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- sources
patch:
operationId: sources_oauth_user_connections_partial_update
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
tags:
- sources
delete:
operationId: sources_oauth_user_connections_delete
description: Source Viewset
parameters: []
responses:
'204':
description: ''
tags:
- sources
parameters:
- name: id
in: path
description: A unique integer value identifying this User OAuth Source Connection.
required: true
type: integer
/sources/saml/:
get:
operationId: sources_saml_list
@ -10963,29 +10968,6 @@ definitions:
attributes:
title: Attributes
type: object
UserOAuthSourceConnection:
description: OAuth Source Serializer
required:
- user
- source
- identifier
type: object
properties:
user:
title: User
type: integer
source:
title: Source
type: string
identifier:
title: Identifier
type: string
maxLength: 255
minLength: 1
access_token:
title: Access token
type: string
x-nullable: true
User:
title: User
description: User Serializer
@ -13742,6 +13724,29 @@ definitions:
title: Callback url
type: string
readOnly: true
UserOAuthSourceConnection:
description: OAuth Source Serializer
required:
- user
- source
- identifier
type: object
properties:
pk:
title: ID
type: integer
readOnly: true
user:
title: User
type: integer
source:
title: Source
type: string
identifier:
title: Identifier
type: string
maxLength: 255
minLength: 1
SAMLSource:
description: SAMLSource Serializer
required:

View File

@ -1,5 +1,6 @@
"""Test Enroll flow"""
from sys import platform
from time import sleep
from typing import Any, Optional
from unittest.case import skipUnless
@ -190,6 +191,7 @@ class TestFlowsEnroll(SeleniumTestCase):
self.driver.close()
self.driver.switch_to.window(self.driver.window_handles[0])
sleep(2)
# We're now logged in
wait = WebDriverWait(
self.get_shadow_root("ak-interface-admin"), self.wait_timeout

View File

@ -94,6 +94,9 @@ export class AppURLManager {
static sourceSAML(slug: string, rest: string): string {
return `/source/saml/${slug}/${rest}`;
}
static sourceOAuth(slug: string, action: string): string {
return `/source/oauth/${action}/${slug}/`;
}
static providerSAML(rest: string): string {
return `/application/saml/${rest}`;
}

View File

@ -24,6 +24,7 @@ import "./settings/UserSettingsAuthenticatorTOTP";
import "./settings/UserSettingsAuthenticatorStatic";
import "./settings/UserSettingsAuthenticatorWebAuthnDevices";
import "./settings/UserSettingsPassword";
import "./settings/SourceSettingsOAuth";
@customElement("ak-user-settings")
export class UserSettingsPage extends LitElement {
@ -35,16 +36,16 @@ export class UserSettingsPage extends LitElement {
renderStageSettings(stage: UserSetting): TemplateResult {
switch (stage.component) {
case "ak-user-settings-authenticator-webauthn":
return html`<ak-user-settings-authenticator-webauthn stageId=${stage.objectUid}>
return html`<ak-user-settings-authenticator-webauthn objectId=${stage.objectUid}>
</ak-user-settings-authenticator-webauthn>`;
case "ak-user-settings-password":
return html`<ak-user-settings-password stageId=${stage.objectUid}>
return html`<ak-user-settings-password objectId=${stage.objectUid}>
</ak-user-settings-password>`;
case "ak-user-settings-authenticator-totp":
return html`<ak-user-settings-authenticator-totp stageId=${stage.objectUid}>
return html`<ak-user-settings-authenticator-totp objectId=${stage.objectUid}>
</ak-user-settings-authenticator-totp>`;
case "ak-user-settings-authenticator-static":
return html`<ak-user-settings-authenticator-static stageId=${stage.objectUid}>
return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}>
</ak-user-settings-authenticator-static>`;
default:
return html`<div class="pf-u-display-flex pf-u-justify-content-center">
@ -57,6 +58,22 @@ export class UserSettingsPage extends LitElement {
}
}
renderSourceSettings(source: UserSetting): TemplateResult {
switch (source.component) {
case "ak-user-settings-source-oauth":
return html`<ak-user-settings-source-oauth objectId=${source.objectUid}>
</ak-user-settings-source-oauth>`;
default:
return html`<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="${ifDefined(source.component)}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>`;
}
}
render(): TemplateResult {
return html`<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1">
@ -89,18 +106,11 @@ export class UserSettingsPage extends LitElement {
</section>`;
});
}))}
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettings({}).then((sources) => {
return sources.map((source) => {
// TODO: Check for non-shell sources
return html`<section slot="page-${source.objectUid}" data-tab-title="${ifDefined(source.title)}" 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">
<ak-site-shell url="${ifDefined(source.component)}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</section>`;
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettings({}).then((source) => {
return source.map((stage) => {
return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile">
${this.renderSourceSettings(stage)}
</section>`;
});
}))}
</ak-tabs>

View File

@ -0,0 +1,50 @@
import { customElement, html, TemplateResult } from "lit-element";
import { BaseUserSettings } from "./BaseUserSettings";
import { OAuthSource, SourcesApi } from "authentik-api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { gettext } from "django";
import { AppURLManager } from "../../../api/legacy";
@customElement("ak-user-settings-source-oauth")
export class SourceSettingsOAuth extends BaseUserSettings {
render(): TemplateResult {
return html`${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthRead({
slug: this.objectId
}).then((source) => {
return html`<div class="pf-c-card">
<div class="pf-pf-c-card__title">
${gettext(`Source ${source.name}`)}
</div>
<div class="pf-c-card__body">
${this.renderInner(source)}
</div>
</div>`;
}))}`;
}
renderInner(source: OAuthSource): TemplateResult {
return html`${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthUserConnectionsList({
source: this.objectId
}).then((connection) => {
if (connection.results.length > 0) {
return html`<p>${gettext("Connected.")}</p>
<button class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthUserConnectionsDelete({
id: connection.results[0].pk || 0
});
}}>
${gettext("Disconnect")}
</button>`;
}
return html`<p>${gettext("Not connected.")}</p>
<a class="pf-c-button pf-m-primary"
href=${AppURLManager.sourceOAuth(source.slug, "login")}>
${gettext("Connect")}
</a>`;
}))}`;
}
}