Compare commits

...

34 Commits

Author SHA1 Message Date
Ken Sternberg
94777fd7dc Merge branch 'main' into web/main/dependency-maint-2 2026-05-12 13:09:47 -07:00
Jens L.
71025a83ad website/docs: release notes for 2025.12.5 and 2026.2.3 (#22310)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-05-12 20:47:41 +02:00
authentik-automation[bot]
00f0cfe6e4 internal: Automated internal backport: CVE-2026-41569.sec.patch to authentik-main (#22301)
* Automated internal backport of patch CVE-2026-41569.sec.patch to authentik-main

* fix spell

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-05-12 20:26:13 +02:00
authentik-automation[bot]
b19f43c8e1 internal: Automated internal backport: CVE-2026-42849.sec.patch to authentik-main (#22303)
* Automated internal backport of patch CVE-2026-42849.sec.patch to authentik-main

* spellcheck

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-05-12 20:21:58 +02:00
authentik-automation[bot]
5053167a05 internal: Automated internal backport: CVE-2026-40166.sec.patch to authentik-main (#22299)
* Automated internal backport of patch CVE-2026-40166.sec.patch to authentik-main

* gen

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-05-12 20:15:56 +02:00
authentik-automation[bot]
f4e868210d internal: Automated internal backport: GHSA-973w-j457-rp2m.sec.patch to authentik-main (#22305)
Automated internal backport of patch GHSA-973w-j457-rp2m.sec.patch to authentik-main

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 20:14:12 +02:00
authentik-automation[bot]
ee954d64f8 internal: Automated internal backport: CVE-2026-41577.sec.patch to authentik-main (#22302)
Automated internal backport of patch CVE-2026-41577.sec.patch to authentik-main

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 20:11:52 +02:00
Tana M Berry
69facf209f website/docs: add mention of drop-down menu, update multiple Integration Guides (#22269)
* test format

* ran make docs

* Updated integration guides with the old label "Create with Provider" to new label of "New Application".

* mention drop-down menu

* add ellipses
2026-05-12 13:09:16 -05:00
Tana M Berry
561cd8c97b website/docs: edit docs about how to add user/service account (#22228)
* edit procedure

* update create a user

* edit first steps doc

* punctuation

* dewi and dominic edits

* typo

* tweak

* more dominic edits

* tweak and ran make install

* tweak and ran uv lock

* edit dir to folder

* wtfci

* undo uv.lock change

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* removed mention of selecting folder

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@goauthentik.io>
2026-05-12 13:09:06 -05:00
Marc 'risson' Schmitt
d14afe242d website/docs: 2026.5 release notes: fix performance improvements wording (#22307)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-05-12 18:02:06 +00:00
Ken Sternberg
349a97b1df web/elements: P5 Drawer component with all capabilities (#21545)
* .

* Did I miss something?

* That was a stupid spelling error.

* ## What

Extend ak-drawer to comply with the full specification; port ak-drawer to use Patternfly 5; vendor the Patternfly 5 subsystems directly responsible for the Drawer into the CSS.

## Why

To meet the requirements of the Drawer, of the LightDOM project, and of the Patternfly 5 vendoring port.

## Details

The Drawer’s internal CSS is now entirely within the Lit framework; the controlling CSS is namespaced to `ak-v2-c--drawer` and placed into the global CSS. Every bit of the drawer has a `part` name, so it can be customized to your heart’s content.

Added stylelint to make sure I’m doing this correctly.

* TSC (!) had opinions.

* Re-arranged to avoid having a 'devDependencies' block.

* Nobody liked this choice.

* Extend ak-drawer to comply with the full specification; port ak-drawer to use Patternfly 5; vendor the Patternfly 5 subsystems directly responsible for the Drawer into the CSS.

This drawer is completely independent of Patternfly 4; it brings everything in-house, everything is under `ak-v2-c-drawer`, and we read our variables from `ak-v2-global` entries as part of the style folder.

The contents of the folder are slotted, so they’re part of the parent DOM and parent CSS context, and can be controlled from there without having to do any magic on the Drawer.

To comply with the standards of the HTML disclosure pattern, the drawer uses `expanded` instead of `open`; it listens for an event to trigger open/close; it emits a `toggle` event when completed. Shortcoming: to completely comply with the disclosure pattern, it should emit a `beforeToggle` to let other clients intercept the request and prevent it from happening, but we don’t do that yet.

Unlike the previous drawer, this one has `resizable`, `position`, `inline/static`, and responsive width breakpoints, all features of the Patternfly 5 React web-component. The resizable variant gives you a visible handle, and even responds to keyborad controls.

Along with the native control through CSS Custom Properties, every part of the component has a `part` declaration, so if you *really* want to customize the thing that’s now possible.

Unlike the Patternfly 5 React version, we impose **no** structure on the internals of the component; no padding, no margin, no header/main/footer segmentation. That pattern is universal, and doesn’t need to be specified for each and every component. If you need that, build it into whatever element you put into the unnamed “main” or `panel` slots.

There is a comprehensive Storybook story page for the component.

To meet the requirements of the Drawer, of the LightDOM project, and of the Patternfly 5 vendoring port.

* Prettier has opinions, as usual.

* UV lockfile update required.

* Restoring from main.

* Merge screwed up the library resolveds again.

* A hail-mary pass.

* Still trying to get this past lint.
2026-05-12 10:47:23 -07:00
authentik-automation[bot]
31d8ddc887 internal: Automated internal backport: CVE-2026-40172.sec.patch to authentik-main (#22300)
Automated internal backport of patch CVE-2026-40172.sec.patch to authentik-main

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 19:46:52 +02:00
authentik-automation[bot]
78f5d85a8b internal: Automated internal backport: GHSA-5wcc-hf24-rf5h.sec.patch to authentik-main (#22304)
Automated internal backport of patch GHSA-5wcc-hf24-rf5h.sec.patch to authentik-main

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 19:37:42 +02:00
authentik-automation[bot]
c2636d72a4 internal: Automated internal backport: CVE-2026-40165.sec.patch to authentik-main (#22298)
Automated internal backport of patch CVE-2026-40165.sec.patch to authentik-main

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 19:37:12 +02:00
Dominic R
f4d6ebf024 website/integrations: EspoCRM: cleanup (#22250) 2026-05-12 14:04:02 +00:00
Ken Sternberg
75a62b7dca web/maintenance: bump Typescript compiler to version 7 (#22172)
* Updgrade Typescript to use Typescript 7 (aka TSGO)

* web: drop `packages/` and composite from `tsc -p .` graph (#22100)

Excluding the workspace subpackages cuts the program graph from 2719 to
1800 non-`node_modules` files (-34%) — most of the drop is the 912
generated files in `packages/client-ts/src/`, which are pulled in by
the recursive include glob even though that package has its own
composite tsconfig and is consumed via `@goauthentik/api/dist/*.d.ts`.

The base `@goauthentik/tsconfig` sets `composite: true`, which forced
TS6307 the moment we tried to exclude `packages/` (`@goauthentik/core`
imports get followed into `web/packages/core/`). Nothing references
`web` in this repo, so disabling composite is safe; `incremental` is
inherited from the base and still drives the `.tsbuildinfo` cache.

On this branch:
  - cold `tsc -p .` 26.3s → 22.7s (-14%)
  - warm `tsc -p .`  4.1s →  3.5s (-15%)
  - `npm run precommit` 39.9s → 37.9s warm

Type coverage is unchanged: each excluded package already type-checks
itself via its own tsconfig + build, and stories/tests/e2e remain in
the include set.

Co-Authored-By: Agent (authentik-i22100-affordable-constant-chartreuse) <279763771+playpen-agent@users.noreply.github.com>

* Fix types.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Agent (authentik-i22100-affordable-constant-chartreuse) <279763771+playpen-agent@users.noreply.github.com>
2026-05-12 15:47:07 +02:00
dependabot[bot]
9581b90961 ci: bump taiki-e/install-action from 2.77.2 to 2.77.3 in /.github/actions/setup (#22261)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.77.2 to 2.77.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](3fa6878dc4...e3134ec54b)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.77.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 13:33:47 +00:00
dependabot[bot]
7dbc01c051 core: bump sentry-sdk from 2.58.0 to 2.59.0 (#22254)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.58.0 to 2.59.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.58.0...2.59.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 13:17:36 +00:00
dependabot[bot]
e188ddc2ab ci: bump github/codeql-action from 4.35.3 to 4.35.4 (#22260)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.3 to 4.35.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.35.3...v4.35.4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 15:16:34 +02:00
dependabot[bot]
ae073544fe core: bump tokio from 1.52.2 to 1.52.3 (#22262)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.2 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.2...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 15:16:23 +02:00
Jens L.
a4e0ae9ecd root: refreshed icon (#22265)
* root: refresh icon

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update pride

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-12 15:14:00 +02:00
Dominic R
086510230d website/integrations: Kanboard: cleanup (#22264) 2026-05-12 11:43:25 +00:00
dependabot[bot]
8d32228c90 web: bump vite from 8.0.10 to 8.0.11 in /web (#22209)
* web: bump vite from 8.0.10 to 8.0.11 in /web

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.10 to 8.0.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump.

* Fix brace expansion.

* Update package ranges.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-05-12 11:41:33 +00:00
Teffen Ellis
1295e2d595 web: remove orphaned chromedriver dependency (#22251)
WebdriverIO was replaced by Playwright in #11598; chromedriver has
been an unused optionalDependency since. Drops 34 transitive packages
(basic-ftp, proxy-agent, pac-proxy-agent, get-uri, ...) and eliminates
roughly 21 dependabot PRs every 6 months with no functional change.

Co-authored-by: Agent <279763771+playpen-agent@users.noreply.github.com>
2026-05-12 13:22:39 +02:00
dependabot[bot]
008c9fb723 web: bump @types/node from 25.6.0 to 25.6.2 in /web (#22257)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.6.0 to 25.6.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 11:15:47 +00:00
Dominic R
9be1b618a5 website/integrations: netbird: cleanup (#21686)
* website/docs: update NetBird integration

* Update index.mdx

Remove comma

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* website/integrations: remove netbird default client type

* website/integrations: clarify netbird entitlements

* website/integrations: refine netbird entitlement steps

* website/integrations: mention netbird entitlements

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-12 07:13:23 -04:00
dependabot[bot]
2afe5b5a7b web: bump globals from 17.5.0 to 17.6.0 in /web (#22259)
Bumps [globals](https://github.com/sindresorhus/globals) from 17.5.0 to 17.6.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.5.0...v17.6.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 13:01:02 +02:00
Tana M Berry
af4ccba51e website/docs: fix link in the 2026.8 Rel Notes to upgrade docs (#22240)
fix link to upgrade docs
2026-05-12 10:53:41 +00:00
Dominic R
d09260f64f website/integrations: HedgeDoc: cleanup (#22248) 2026-05-12 06:46:07 -04:00
Dominic R
923c1f465a website/integrations: AFFiNE: cleanup (#22249) 2026-05-12 06:45:43 -04:00
dependabot[bot]
e5208185f9 web: bump uuid from 11.1.0 to 14.0.0 in /web (#22253)
Bumps [uuid](https://github.com/uuidjs/uuid) from 11.1.0 to 14.0.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v11.1.0...v14.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 12:41:57 +02:00
Dominic R
b5deeaa822 enterprise: fix account lockdown target handling (#22246)
- Use the pending lockdown target in the example blueprint warning and avoid repeating the username when email/name is not distinct.

- Hide the admin Account Lockdown action for internal service accounts.
2026-05-12 01:59:00 +00:00
dependabot[bot]
cceb952429 web: bump uuid and mermaid in /web (#22239) 2026-05-12 02:42:44 +02:00
Ken Sternberg
1b03c36c86 web/maint: Move notifications into the components folder; adjust imports accordingly
# What

1.  Moves the notifications folder from elements to components: the API and Notifications drawers are API-aware. If we want to separate that out and do something unique, we can, but for now, let’s just get things where they should be.

2.  Adjusts all the imports correctly.

3.  (Minor): Mutating the array and then calling `requestUpdate()`, especially when the array is then sorted-and-reversed, doesn’t save anything over creating a new array with the new item shifted onto the head, sorted once, and then saved to the property, which triggers an update automatically.
2026-05-11 14:53:24 -07:00
155 changed files with 3948 additions and 1362 deletions

View File

@@ -64,7 +64,7 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@3fa6878dc4ae603f73960271565a082bf196ab96 # v2
uses: taiki-e/install-action@e3134ec54b36203e18f2d1e80652058bd078dd91 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web)

View File

@@ -28,10 +28,10 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.35.3
uses: github/codeql-action/init@v4.35.4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4.35.3
uses: github/codeql-action/autobuild@v4.35.4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.35.3
uses: github/codeql-action/analyze@v4.35.4

4
Cargo.lock generated
View File

@@ -3924,9 +3924,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.52.2"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",

View File

@@ -97,7 +97,7 @@ sqlx = { version = "= 0.8.6", default-features = false, features = [
tempfile = "= 3.27.0"
thiserror = "= 2.0.18"
time = { version = "= 0.3.47", features = ["macros"] }
tokio = { version = "= 1.52.2", features = ["full", "tracing"] }
tokio = { version = "= 1.52.3", features = ["full", "tracing"] }
tokio-retry2 = "= 0.9.1"
tokio-rustls = "= 0.26.4"
tokio-util = { version = "= 0.7.18", features = ["full"] }

View File

@@ -42,11 +42,29 @@ def validate_auth(header: bytes, format="bearer") -> str | None:
return auth_credentials
class IPCUser(AnonymousUser):
class VirtualUser(AnonymousUser):
is_active = True
@property
def type(self):
return UserTypes.INTERNAL_SERVICE_ACCOUNT
@property
def is_anonymous(self):
return False
@property
def is_authenticated(self):
return True
def all_roles(self):
return []
class IPCUser(VirtualUser):
"""'Virtual' user for IPC communication between authentik core and the authentik router"""
username = "authentik:system"
is_active = True
is_superuser = True
@property
@@ -62,17 +80,6 @@ class IPCUser(AnonymousUser):
def has_module_perms(self, module):
return True
@property
def is_anonymous(self):
return False
@property
def is_authenticated(self):
return True
def all_roles(self):
return []
class TokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Bearer authentication"""

View File

@@ -246,6 +246,25 @@ class GroupSerializer(ModelSerializer):
)
return superuser
def validate_users(self, users: list) -> list:
"""Require add_user_to_group permission when adding new members via group PATCH."""
request: Request = self.context.get("request", None)
if not request:
return users
if not self.instance:
return users
# BulkManyRelatedField returns raw PKs, not model instances
current_user_pks = set(self.instance.users.values_list("pk", flat=True))
new_users = [u for u in users if u not in current_user_pks]
if not new_users:
return users
has_perm = request.user.has_perm(
"authentik_core.add_user_to_group"
) or request.user.has_perm("authentik_core.add_user_to_group", self.instance)
if not has_perm:
raise ValidationError(_("User does not have permission to add members to this group."))
return users
class Meta:
model = Group
fields = [

View File

@@ -297,6 +297,36 @@ class UserSerializer(ModelSerializer):
raise ValidationError(_("Setting a user to internal service account is not allowed."))
return user_type
def validate_groups(self, groups: list) -> list:
"""Require enable_group_superuser permission when adding a user to a superuser group."""
request: Request = self.context.get("request", None)
if not request:
return groups
current_groups = set(self.instance.groups.all()) if self.instance else set()
for group in groups:
if not group.is_superuser:
continue
if group in current_groups:
continue
if not request.user.has_perm("authentik_core.enable_group_superuser"):
raise ValidationError(
_("User does not have permission to add members to a superuser group.")
)
return groups
def validate_roles(self, roles: list) -> list:
"""Require change_role permission when assigning new roles to a user."""
request: Request = self.context.get("request", None)
if not request:
return roles
current_roles = set(self.instance.roles.all()) if self.instance else set()
new_roles = [r for r in roles if r not in current_roles]
if not new_roles:
return roles
if not request.user.has_perm("authentik_rbac.change_role"):
raise ValidationError(_("User does not have permission to assign roles."))
return roles
def validate(self, attrs: dict) -> dict:
if self.instance and self.instance.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
raise ValidationError(_("Can't modify internal service account users"))

View File

@@ -158,3 +158,58 @@ class TestGroupsAPI(APITestCase):
data={"name": generate_id(), "is_superuser": True},
)
self.assertEqual(res.status_code, 201)
def test_patch_users_no_perm(self):
"""PATCH group with new users without add_user_to_group must be rejected."""
group = Group.objects.create(name=generate_id())
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
data={"users": [self.user.pk]},
content_type="application/json",
)
self.assertEqual(res.status_code, 400)
def test_patch_users_with_global_perm(self):
"""PATCH group with new users with global add_user_to_group must succeed."""
group = Group.objects.create(name=generate_id())
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group")
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
data={"users": [self.user.pk]},
content_type="application/json",
)
self.assertEqual(res.status_code, 200)
def test_patch_users_with_obj_perm(self):
"""PATCH group with new users with object-level add_user_to_group must succeed."""
group = Group.objects.create(name=generate_id())
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group", group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
data={"users": [self.user.pk]},
content_type="application/json",
)
self.assertEqual(res.status_code, 200)
def test_patch_existing_users_no_perm(self):
"""PATCH group keeping existing membership without add_user_to_group must succeed."""
group = Group.objects.create(name=generate_id())
group.users.add(self.user)
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
self.client.force_login(self.login_user)
res = self.client.patch(
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
data={"users": [self.user.pk]},
content_type="application/json",
)
self.assertEqual(res.status_code, 200)

View File

@@ -12,6 +12,7 @@ from authentik.brands.models import Brand
from authentik.core.models import (
USER_ATTRIBUTE_TOKEN_EXPIRING,
AuthenticatedSession,
Group,
Session,
Token,
User,
@@ -25,6 +26,7 @@ from authentik.core.tests.utils import (
)
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation
from authentik.lib.generators import generate_id, generate_key
from authentik.rbac.models import Role
from authentik.stages.email.models import EmailStage
INVALID_PASSWORD_HASH = "not-a-valid-hash"
@@ -939,3 +941,79 @@ class TestUsersAPI(APITestCase):
self.assertIn(user2.pk, pks)
# Verify user2 comes before user1 in descending order
self.assertLess(pks.index(user2.pk), pks.index(user1.pk))
class TestUsersAPIGroupRoleValidation(APITestCase):
"""Test that PATCH /api/v3/core/users/{pk}/ enforces group and role permission checks."""
def setUp(self) -> None:
self.actor = create_test_user()
self.target = create_test_user()
def _patch(self, data: dict):
self.client.force_login(self.actor)
return self.client.patch(
reverse("authentik_api:user-detail", kwargs={"pk": self.target.pk}),
data=data,
content_type="application/json",
)
def test_patch_superuser_group_no_perm(self):
"""Assigning a superuser group without enable_group_superuser must be rejected."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
group = Group.objects.create(name=generate_id(), is_superuser=True)
res = self._patch({"groups": [str(group.pk)]})
self.assertEqual(res.status_code, 400)
def test_patch_superuser_group_with_perm(self):
"""Assigning a superuser group with enable_group_superuser must succeed."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
self.actor.assign_perms_to_managed_role("authentik_core.enable_group_superuser")
group = Group.objects.create(name=generate_id(), is_superuser=True)
res = self._patch({"groups": [str(group.pk)]})
self.assertEqual(res.status_code, 200)
def test_patch_non_superuser_group_no_perm(self):
"""Assigning a non-superuser group without special permission must succeed."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
group = Group.objects.create(name=generate_id(), is_superuser=False)
res = self._patch({"groups": [str(group.pk)]})
self.assertEqual(res.status_code, 200)
def test_patch_existing_superuser_group_no_perm(self):
"""Keeping an existing superuser group membership without the permission must succeed."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
group = Group.objects.create(name=generate_id(), is_superuser=True)
self.target.groups.add(group)
res = self._patch({"groups": [str(group.pk)]})
self.assertEqual(res.status_code, 200)
def test_patch_role_no_perm(self):
"""Assigning a new role without change_role must be rejected."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
role = Role.objects.create(name=generate_id())
res = self._patch({"roles": [str(role.pk)]})
self.assertEqual(res.status_code, 400)
def test_patch_role_with_perm(self):
"""Assigning a new role with change_role must succeed."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
self.actor.assign_perms_to_managed_role("authentik_rbac.change_role")
role = Role.objects.create(name=generate_id())
res = self._patch({"roles": [str(role.pk)]})
self.assertEqual(res.status_code, 200)
def test_patch_existing_role_no_perm(self):
"""Keeping an existing role without change_role must succeed."""
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
role = Role.objects.create(name=generate_id())
self.target.roles.add(role)
res = self._patch({"roles": [str(role.pk)]})
self.assertEqual(res.status_code, 200)

View File

@@ -7,7 +7,7 @@ from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_sche
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import ChoiceField
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
from rest_framework.response import Response
@@ -44,7 +44,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_ME
class AgentConnectorSerializer(ConnectorSerializer):
class Meta(ConnectorSerializer.Meta):
model = AgentConnector
fields = ConnectorSerializer.Meta.fields + [
@@ -63,7 +62,6 @@ class AgentConnectorSerializer(ConnectorSerializer):
class MDMConfigSerializer(PassiveSerializer):
platform = ChoiceField(choices=OSFamily.choices)
enrollment_token = PrimaryKeyRelatedField(
queryset=EnrollmentToken.objects.including_expired().all()
@@ -89,7 +87,6 @@ class AgentConnectorViewSet(
UsedByMixin,
ModelViewSet,
):
queryset = AgentConnector.objects.all()
serializer_class = AgentConnectorSerializer
search_fields = ["name"]
@@ -121,6 +118,8 @@ class AgentConnectorViewSet(
methods=["POST"],
detail=False,
authentication_classes=[AgentEnrollmentAuth],
# Permissions are handled via AgentEnrollmentAuth
permission_classes=[AllowAny],
)
def enroll(self, request: Request):
token: EnrollmentToken = request.auth
@@ -151,7 +150,13 @@ class AgentConnectorViewSet(
request=OpenApiTypes.NONE,
responses=AgentConfigSerializer(),
)
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
@action(
methods=["GET"],
detail=False,
authentication_classes=[AgentAuth],
# Permissions are handled via AgentAuth
permission_classes=[AllowAny],
)
def agent_config(self, request: Request):
token: DeviceToken = request.auth
connector: AgentConnector = token.device.connector.agentconnector
@@ -165,7 +170,13 @@ class AgentConnectorViewSet(
request=DeviceFacts(),
responses={204: OpenApiResponse(description="Successfully checked in")},
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentAuth],
# Permissions are handled via AgentAuth
permission_classes=[AllowAny],
)
def check_in(self, request: Request):
token: DeviceToken = request.auth
data = DeviceFacts(data=request.data)

View File

@@ -1,5 +1,6 @@
from typing import Any
from django.db.models import Model
from django.http import HttpRequest
from django.utils.timezone import now
from drf_spectacular.extensions import OpenApiAuthenticationExtension
@@ -9,7 +10,7 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.api.authentication import IPCUser, validate_auth
from authentik.api.authentication import VirtualUser, validate_auth
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import User
from authentik.crypto.apps import MANAGED_KEY
@@ -25,9 +26,19 @@ LOGGER = get_logger()
PLATFORM_ISSUER = "goauthentik.io/platform"
class DeviceUser(IPCUser):
class DeviceUser(VirtualUser):
username = "authentik:endpoints:device"
def has_perm(self, perm: str, obj: Model | None = None) -> bool:
print(perm)
if perm in [
"authentik_core.view_user",
"authentik_core.view_group",
]:
return True
return False
class AgentEnrollmentAuth(BaseAuthentication):

View File

@@ -223,3 +223,17 @@ class TestAgentAPI(APITestCase):
data={"platform": OSFamily.macOS, "enrollment_token": self.token.pk},
)
self.assertEqual(res.status_code, 200)
def test_users_list(self):
response = self.client.get(
reverse("authentik_api:user-list"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
def test_other_api_forbidden(self):
response = self.client.get(
reverse("authentik_api:application-list"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 403)

View File

@@ -2,6 +2,7 @@ from django.urls import reverse
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from structlog.stdlib import get_logger
@@ -25,7 +26,13 @@ class AgentConnectorViewSetMixin:
request=OpenApiTypes.NONE,
responses=AgentAuthenticationResponse(),
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentAuth],
# Permissions are handled via AgentAuth
permission_classes=[AllowAny],
)
@enterprise_action
def auth_ia(self, request: Request) -> Response:
token: DeviceToken = request.auth

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
from urllib.parse import urlparse
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
@@ -55,7 +56,9 @@ class SignInRequest:
_, provider = req.get_app_provider()
if not req.wreply:
req.wreply = provider.acs_url
if not req.wreply.startswith(provider.acs_url):
reply = urlparse(req.wreply)
configured = urlparse(provider.acs_url)
if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)):
raise ValueError("Invalid wreply")
return req

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
from urllib.parse import urlparse
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
@@ -32,7 +33,9 @@ class SignOutRequest:
_, provider = req.get_app_provider()
if not req.wreply:
req.wreply = provider.acs_url
if not req.wreply.startswith(provider.acs_url):
reply = urlparse(req.wreply)
configured = urlparse(provider.acs_url)
if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)):
raise ValueError("Invalid wreply")
return req

View File

@@ -27,12 +27,27 @@ class TestWSFedSignIn(TestCase):
name=generate_id(),
authorization_flow=self.flow,
signing_kp=self.cert,
acs_url="https://t.goauthentik.io",
audience="foo",
)
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.provider
)
self.factory = RequestFactory()
def test_wreply(self):
request = self.factory.get(
"/?wreply=https://t.goauthentik.io/foo&wa=wsignin1.0&wtrealm=foo",
user=get_anonymous_user(),
)
SignInRequest.parse(request)
with self.assertRaises(ValueError):
request = self.factory.get(
"/?wreply=https://t.goauthentik.io.invalid.com&wa=wsignin1.0&wtrealm=foo",
user=get_anonymous_user(),
)
SignInRequest.parse(request)
def test_token_gen(self):
request = self.factory.get("/", user=get_anonymous_user())
proc = SignInProcessor(

View File

@@ -9,10 +9,10 @@ from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import MetaNameSerializer, ModelSerializer
from authentik.providers.oauth2.api.providers import OAuth2ProviderSerializer
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
@@ -20,7 +20,7 @@ class ExpiringBaseGrantModelSerializer(ModelSerializer, MetaNameSerializer):
"""Serializer for BaseGrantModel and ExpiringBaseGrant"""
user = UserSerializer()
provider = OAuth2ProviderSerializer()
provider = ProviderSerializer()
scope = ListField(child=CharField())
class Meta:

View File

@@ -1,6 +1,7 @@
"""authentik saml source processor"""
from base64 import b64decode
from datetime import UTC, datetime
from time import mktime
from typing import TYPE_CHECKING
@@ -40,6 +41,7 @@ from authentik.sources.saml.exceptions import (
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
SAMLException,
UnsupportedNameIDFormat,
)
from authentik.sources.saml.models import (
@@ -95,6 +97,7 @@ class ResponseProcessor:
self._verify_request_id()
self._verify_status()
self._verify_conditions()
def _decrypt_response(self):
"""Decrypt SAMLResponse EncryptedAssertion Element"""
@@ -126,6 +129,20 @@ class ResponseProcessor:
)
self._assertion = decrypted_assertion
def _verify_conditions(self):
conditions = self.get_assertion().find(f"{{{NS_SAML_ASSERTION}}}Conditions")
if conditions is None:
return
_now = now()
before = conditions.attrib.get("NotBefore")
if before:
if datetime.fromisoformat(before).replace(tzinfo=UTC) > _now:
raise SAMLException("Assertion is not valid yet or expired.")
on_or_after = conditions.attrib.get("NotOnOrAfter")
if on_or_after:
if datetime.fromisoformat(on_or_after).replace(tzinfo=UTC) < _now:
raise SAMLException("Assertion is not valid yet or expired.")
def _verify_signature(self, signature_node: _Element):
"""Verify a single signature node"""
xmlsec.tree.add_ids(self._root, ["ID"])
@@ -215,10 +232,9 @@ class ResponseProcessor:
user has an attribute that refers to our Source for cleanup. The user is also deleted
on logout and periodically."""
# Create a temporary User
name_id = self._get_name_id()
username = name_id.text
name_id_el, name_id = self._get_name_id()
# trim username to ensure it is max 150 chars
username = f"ak-{username[: USERNAME_MAX_LENGTH - 14]}-transient"
username = f"ak-{name_id[: USERNAME_MAX_LENGTH - 14]}-transient"
expiry = mktime(
(now() + timedelta_from_string(self._source.temporary_user_delete_after)).timetuple()
)
@@ -234,20 +250,18 @@ class ResponseProcessor:
},
path=self._source.get_user_path(),
)
LOGGER.debug("Created temporary user for NameID Transient", username=name_id.text)
LOGGER.debug("Created temporary user for NameID Transient", username=name_id)
user.set_unusable_password()
user.save()
UserSAMLSourceConnection.objects.create(
source=self._source, user=user, identifier=name_id.text
)
UserSAMLSourceConnection.objects.create(source=self._source, user=user, identifier=name_id)
return SAMLSourceFlowManager(
source=self._source,
request=self._http_request,
identifier=str(name_id.text),
identifier=str(name_id),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
"name_id": name_id_el,
},
policy_context={},
)
@@ -258,7 +272,7 @@ class ResponseProcessor:
return self._assertion
return self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
def _get_name_id(self) -> Element:
def _get_name_id(self) -> tuple[Element, str]:
"""Get NameID Element"""
assertion = self.get_assertion()
if assertion is None:
@@ -269,12 +283,11 @@ class ResponseProcessor:
name_id = subject.find(f"{{{NS_SAML_ASSERTION}}}NameID")
if name_id is None:
raise ValueError("NameID element not found")
return name_id
return name_id, "".join(name_id.itertext())
def _get_name_id_filter(self) -> dict[str, str]:
"""Returns the subject's NameID as a Filter for the `User`"""
name_id_el = self._get_name_id()
name_id = name_id_el.text
name_id_el, name_id = self._get_name_id()
if not name_id:
raise UnsupportedNameIDFormat("Subject's NameID is empty.")
_format = name_id_el.attrib["Format"]
@@ -295,26 +308,26 @@ class ResponseProcessor:
def prepare_flow_manager(self) -> SourceFlowManager:
"""Prepare flow plan depending on whether or not the user exists"""
name_id = self._get_name_id()
name_id_el, name_id = self._get_name_id()
# Sanity check, show a warning if NameIDPolicy doesn't match what we go
if self._source.name_id_policy != name_id.attrib["Format"]:
if self._source.name_id_policy != name_id_el.attrib["Format"]:
LOGGER.warning(
"NameID from IdP doesn't match our policy",
expected=self._source.name_id_policy,
got=name_id.attrib["Format"],
got=name_id_el.attrib["Format"],
)
# transient NameIDs are handled separately as they don't have to go through flows.
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
if name_id_el.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
return self._handle_name_id_transient()
return SAMLSourceFlowManager(
source=self._source,
request=self._http_request,
identifier=str(name_id.text),
identifier=str(name_id),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
"name_id": name_id_el,
},
policy_context={
"saml_response": etree.tostring(self._root),

View File

@@ -4,6 +4,7 @@ from base64 import b64encode
from defusedxml.lxml import fromstring
from django.test import TestCase
from freezegun import freeze_time
from authentik.common.saml.constants import NS_SAML_ASSERTION
from authentik.core.tests.utils import RequestFactory, create_test_flow
@@ -34,6 +35,7 @@ class TestPropertyMappings(TestCase):
pre_authentication_flow=create_test_flow(),
)
@freeze_time("2022-10-14T14:15:00")
def test_user_base_properties(self):
"""Test user base properties"""
properties = self.source.get_base_user_properties(
@@ -61,6 +63,7 @@ class TestPropertyMappings(TestCase):
properties = self.source.get_base_group_properties(root=ROOT, group_id=group_id)
self.assertEqual(properties, {"name": group_id})
@freeze_time("2022-10-14T14:15:00")
def test_user_property_mappings(self):
"""Test user property mappings"""
self.source.user_property_mappings.add(
@@ -94,6 +97,7 @@ class TestPropertyMappings(TestCase):
},
)
@freeze_time("2022-10-14T14:15:00")
def test_group_property_mappings(self):
"""Test group property mappings"""
self.source.group_property_mappings.add(

View File

@@ -3,6 +3,7 @@
from base64 import b64encode
from django.test import TestCase
from freezegun import freeze_time
from authentik.core.tests.utils import RequestFactory, create_test_cert, create_test_flow
from authentik.crypto.models import CertificateKeyPair
@@ -46,6 +47,7 @@ class TestResponseProcessor(TestCase):
):
ResponseProcessor(self.source, request).parse()
@freeze_time("2022-10-14T14:15:00")
def test_success(self):
"""Test success"""
request = self.factory.post(
@@ -72,6 +74,7 @@ class TestResponseProcessor(TestCase):
},
)
@freeze_time("2022-10-14T14:16:40Z")
def test_success_with_status_message_and_detail(self):
"""Test success with StatusMessage and StatusDetail present (should not raise error)"""
request = self.factory.post(
@@ -88,6 +91,7 @@ class TestResponseProcessor(TestCase):
sfm = parser.prepare_flow_manager()
self.assertEqual(sfm.user_properties["username"], "jens@goauthentik.io")
@freeze_time("2022-10-14T14:16:40Z")
def test_error_with_message_and_detail(self):
"""Test error status with StatusMessage and StatusDetail includes both in error"""
request = self.factory.post(
@@ -105,6 +109,7 @@ class TestResponseProcessor(TestCase):
self.assertIn("User account is disabled", str(ctx.exception))
self.assertIn("Authentication failed", str(ctx.exception))
@freeze_time("2024-08-07T15:48:09.325Z")
def test_encrypted_correct(self):
"""Test encrypted"""
key = load_fixture("fixtures/encrypted-key.pem")
@@ -142,6 +147,7 @@ class TestResponseProcessor(TestCase):
with self.assertRaises(InvalidEncryption):
parser.parse()
@freeze_time("2022-10-14T14:16:40Z")
def test_verification_assertion(self):
"""Test verifying signature inside assertion"""
key = load_fixture("fixtures/signature_cert.pem")
@@ -164,6 +170,7 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
@freeze_time("2014-07-17T01:02:18Z")
def test_verification_assertion_duplicate(self):
"""Test verifying signature inside assertion, where the response has another assertion
before our signed assertion"""
@@ -186,9 +193,35 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
self.assertNotEqual(parser._get_name_id().text, "bad")
self.assertEqual(parser._get_name_id().text, "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
self.assertNotEqual(parser._get_name_id()[1], "bad")
self.assertEqual(parser._get_name_id()[1], "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
@freeze_time("2022-10-14T14:15:00")
def test_name_id_comment(self):
"""Test comment in name ID"""
fixture = load_fixture("fixtures/response_signed_assertion_dup.xml")
fixture = fixture.replace(
"_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7",
"_ce3d2948b4cf20146dee0a0b3dd6f<!--x-->69b6cf86f62d7",
)
key = load_fixture("fixtures/signature_cert.pem")
kp = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data=key,
)
self.source.verification_kp = kp
self.source.signed_assertion = True
self.source.signed_response = False
request = self.factory.post(
"/",
data={"SAMLResponse": b64encode(fixture.encode()).decode()},
)
parser = ResponseProcessor(self.source, request)
parser.parse()
self.assertEqual(parser._get_name_id()[1], "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
@freeze_time("2014-07-17T01:02:18Z")
def test_verification_response(self):
"""Test verifying signature inside response"""
key = load_fixture("fixtures/signature_cert.pem")
@@ -211,6 +244,7 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
@freeze_time("2024-01-18T06:20:48Z")
def test_verification_response_and_assertion(self):
"""Test verifying signature inside response and assertion"""
key = load_fixture("fixtures/signature_cert.pem")
@@ -257,6 +291,7 @@ class TestResponseProcessor(TestCase):
with self.assertRaisesMessage(InvalidSignature, ""):
parser.parse()
@freeze_time("2022-10-14T14:15:00")
def test_verification_no_signature(self):
"""Test rejecting response without signature when signed_assertion is True"""
key = load_fixture("fixtures/signature_cert.pem")
@@ -303,6 +338,7 @@ class TestResponseProcessor(TestCase):
with self.assertRaisesMessage(InvalidSignature, ""):
parser.parse()
@freeze_time("2025-10-30T05:45:47.619Z")
def test_signed_encrypted_response(self):
"""Test signed & encrypted response"""
verification_key = load_fixture("fixtures/signature_cert2.pem")
@@ -330,6 +366,7 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
@freeze_time("2026-01-21T14:23")
def test_transient(self):
"""Test SAML transient NameID"""
verification_key = load_fixture("fixtures/signature_cert2.pem")

View File

@@ -4,6 +4,7 @@ from base64 import b64encode
from django.test import RequestFactory, TestCase
from django.urls import reverse
from freezegun import freeze_time
from authentik.core.tests.utils import create_test_flow
from authentik.flows.planner import PLAN_CONTEXT_REDIRECT, FlowPlan
@@ -26,6 +27,7 @@ class TestViews(TestCase):
pre_authentication_flow=create_test_flow(),
)
@freeze_time("2022-10-14T14:15:00")
def test_enroll(self):
"""Enroll"""
flow = create_test_flow()
@@ -52,6 +54,7 @@ class TestViews(TestCase):
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
self.assertIsNotNone(plan)
@freeze_time("2022-10-14T14:15:00")
def test_enroll_redirect(self):
"""Enroll when attempting to access a provider"""
initial_redirect = f"http://{generate_id()}"

View File

@@ -36,14 +36,10 @@ entries:
attrs:
order: 50
initial_value: |
target_uuid = (http_request.session.get("authentik/flows/get", {}) or {}).get("user_uuid")
current_user_uuid = str(getattr(user, "pk", "") or getattr(http_request.user, "pk", ""))
is_self_service = not target_uuid or target_uuid == current_user_uuid
pending_user = None
if target_uuid and not is_self_service:
from authentik.core.models import User
pending_user = User.objects.filter(pk=target_uuid).first()
actor_uuid = str(getattr(http_request.user, "pk", ""))
pending_user = user if getattr(user, "is_authenticated", False) else None
target_uuid = str(getattr(pending_user, "pk", ""))
is_self_service = not target_uuid or target_uuid == actor_uuid
if is_self_service:
return (
"<p><strong>You are about to lock down your own account.</strong></p>"
@@ -63,14 +59,15 @@ entries:
from django.utils.html import escape
if pending_user:
email = escape(pending_user.email or pending_user.name or "No email")
user_html = f"<p><code>{escape(pending_user.username)}</code> ({email})</p>"
detail = pending_user.email or pending_user.name
user_html = f"<code>{escape(pending_user.username)}</code>"
if detail and detail != pending_user.username:
user_html = f"{user_html} ({escape(detail)})"
else:
user_html = "<p>the account selected when this one-time lockdown link was created</p>"
user_html = "the account selected when this one-time lockdown link was created"
return (
"<p><strong>You are about to lock down the following account:</strong></p>"
f"{user_html}"
f"<p><strong>You are about to lock down the following account:</strong> {user_html}</p>"
"<p>This is an emergency action for cutting off access to the account right away. "
"It does not lock the administrator who opened this page.</p>"
"<p><strong>This will immediately:</strong></p>"
@@ -99,9 +96,9 @@ entries:
attrs:
order: 100
initial_value: |
target_uuid = (http_request.session.get("authentik/flows/get", {}) or {}).get("user_uuid")
current_user_uuid = str(getattr(user, "pk", "") or getattr(http_request.user, "pk", ""))
is_self_service = not target_uuid or target_uuid == current_user_uuid
actor_uuid = str(getattr(http_request.user, "pk", ""))
target_uuid = str(getattr(user, "pk", ""))
is_self_service = not target_uuid or target_uuid == actor_uuid
if is_self_service:
info = (
"Use this if you no longer trust your current password or sessions. "
@@ -134,9 +131,9 @@ entries:
attrs:
order: 200
placeholder: |
target_uuid = (http_request.session.get("authentik/flows/get", {}) or {}).get("user_uuid")
current_user_uuid = str(getattr(user, "pk", "") or getattr(http_request.user, "pk", ""))
is_self_service = not target_uuid or target_uuid == current_user_uuid
actor_uuid = str(getattr(http_request.user, "pk", ""))
target_uuid = str(getattr(user, "pk", ""))
is_self_service = not target_uuid or target_uuid == actor_uuid
if is_self_service:
return "Describe why you are locking your account..."
return "Describe why this account is being locked down..."
@@ -184,14 +181,10 @@ entries:
attrs:
order: 300
initial_value: |
target_uuid = (http_request.session.get("authentik/flows/get", {}) or {}).get("user_uuid")
from django.utils.html import escape
from authentik.core.models import User
if target_uuid:
target = User.objects.filter(pk=target_uuid).first()
if target:
return f"<p><code>{escape(target.username)}</code> has been locked down.</p>"
if getattr(user, "is_authenticated", False):
return f"<p><code>{escape(user.username)}</code> has been locked down.</p>"
return "<p>The selected account has been locked down.</p>"
initial_value_expression: true
@@ -221,9 +214,9 @@ entries:
attrs:
name: default-account-lockdown-admin-policy
expression: |
target_uuid = (request.http_request.session.get("authentik/flows/get", {}) or {}).get("user_uuid")
current_user_uuid = str(getattr(request.user, "pk", "") or getattr(request.http_request.user, "pk", ""))
return bool(target_uuid) and target_uuid != current_user_uuid
actor_uuid = str(getattr(request.http_request.user, "pk", ""))
target_uuid = str(getattr(request.user, "pk", ""))
return bool(target_uuid) and target_uuid != actor_uuid
identifiers:
name: default-account-lockdown-admin-policy
id: admin-policy

View File

@@ -110,17 +110,6 @@ func (a *Application) getTraefikForwardUrl(r *http.Request) (*url.URL, error) {
// getNginxForwardUrl See https://github.com/kubernetes/ingress-nginx/blob/main/rootfs/etc/nginx/template/nginx.tmpl
func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
ou := r.Header.Get("X-Original-URI")
if ou != "" {
// Turn this full URL into a relative URL
u := &url.URL{
Host: "",
Scheme: "",
Path: ou,
}
a.log.WithField("url", u.String()).Info("building forward URL from X-Original-URI")
return u, nil
}
h := r.Header.Get("X-Original-URL")
if len(h) < 1 {
return nil, errors.New("no forward URL found")

View File

@@ -5,10 +5,8 @@ import (
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"goauthentik.io/internal/outpost/proxyv2/constants"
"goauthentik.io/internal/outpost/proxyv2/types"
api "goauthentik.io/packages/client-go"
)
@@ -47,67 +45,6 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
}
func TestForwardHandleNginx_Single_URI(t *testing.T) {
a := newTestApplication()
req, _ := http.NewRequest("GET", "https://foo.bar/outpost.goauthentik.io/auth/nginx", nil)
req.Header.Set("X-Original-URI", "/app")
rr := httptest.NewRecorder()
a.forwardHandleNginx(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
s, _ := a.sessions.Get(req, a.SessionName())
assert.Equal(t, "/app", s.Values[constants.SessionRedirect])
}
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
a := newTestApplication()
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
req.Header.Set("X-Original-URI", "/")
rr := httptest.NewRecorder()
a.forwardHandleNginx(rr, req)
s, _ := a.sessions.Get(req, a.SessionName())
s.ID = uuid.New().String()
s.Options.MaxAge = 86400
s.Values[constants.SessionClaims] = types.Claims{
Sub: "foo",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]any{
"username": "foo",
"password": "bar",
"additionalHeaders": map[string]any{
"foo": "bar",
},
},
},
}
err := a.sessions.Save(req, rr, s)
if err != nil {
panic(err)
}
rr = httptest.NewRecorder()
a.forwardHandleNginx(rr, req)
h := rr.Result().Header
assert.Equal(t, []string{"Basic Zm9vOmJhcg=="}, h["Authorization"])
assert.Equal(t, []string{"bar"}, h["Foo"])
assert.Equal(t, []string{""}, h["User-Agent"])
assert.Equal(t, []string{""}, h["X-Authentik-Email"])
assert.Equal(t, []string{""}, h["X-Authentik-Groups"])
assert.Equal(t, []string{""}, h["X-Authentik-Jwt"])
assert.Equal(t, []string{""}, h["X-Authentik-Meta-App"])
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Jwks"])
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Outpost"])
assert.Equal(t, []string{""}, h["X-Authentik-Name"])
assert.Equal(t, []string{"foo"}, h["X-Authentik-Uid"])
assert.Equal(t, []string{""}, h["X-Authentik-Username"])
}
func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()

View File

@@ -11,3 +11,4 @@ Naur
Wärting
Aadit
Kilby
Kahmen

View File

@@ -164,3 +164,4 @@ yamltags
zxcvbn
~uuid
~uuids
wreply

View File

@@ -12,8 +12,8 @@
* Do not edit the class manually.
*/
import type { OAuth2Provider } from "./OAuth2Provider";
import { OAuth2ProviderFromJSON, OAuth2ProviderToJSON } from "./OAuth2Provider";
import type { Provider } from "./Provider";
import { ProviderFromJSON, ProviderToJSON } from "./Provider";
import type { User } from "./User";
import { UserFromJSON, UserToJSON } from "./User";
@@ -31,10 +31,10 @@ export interface ExpiringBaseGrantModel {
readonly pk: number;
/**
*
* @type {OAuth2Provider}
* @type {Provider}
* @memberof ExpiringBaseGrantModel
*/
provider: OAuth2Provider;
provider: Provider;
/**
*
* @type {User}
@@ -86,7 +86,7 @@ export function ExpiringBaseGrantModelFromJSONTyped(
}
return {
pk: json["pk"],
provider: OAuth2ProviderFromJSON(json["provider"]),
provider: ProviderFromJSON(json["provider"]),
user: UserFromJSON(json["user"]),
isExpired: json["is_expired"],
expires: json["expires"] == null ? undefined : new Date(json["expires"]),
@@ -107,7 +107,7 @@ export function ExpiringBaseGrantModelToJSONTyped(
}
return {
provider: OAuth2ProviderToJSON(value["provider"]),
provider: ProviderToJSON(value["provider"]),
user: UserToJSON(value["user"]),
expires: value["expires"] == null ? value["expires"] : value["expires"].toISOString(),
scope: value["scope"],

View File

@@ -12,8 +12,8 @@
* Do not edit the class manually.
*/
import type { OAuth2Provider } from "./OAuth2Provider";
import { OAuth2ProviderFromJSON, OAuth2ProviderToJSON } from "./OAuth2Provider";
import type { Provider } from "./Provider";
import { ProviderFromJSON, ProviderToJSON } from "./Provider";
import type { User } from "./User";
import { UserFromJSON, UserToJSON } from "./User";
@@ -31,10 +31,10 @@ export interface TokenModel {
readonly pk: number;
/**
*
* @type {OAuth2Provider}
* @type {Provider}
* @memberof TokenModel
*/
provider: OAuth2Provider;
provider: Provider;
/**
*
* @type {User}
@@ -96,7 +96,7 @@ export function TokenModelFromJSONTyped(json: any, ignoreDiscriminator: boolean)
}
return {
pk: json["pk"],
provider: OAuth2ProviderFromJSON(json["provider"]),
provider: ProviderFromJSON(json["provider"]),
user: UserFromJSON(json["user"]),
isExpired: json["is_expired"],
expires: json["expires"] == null ? undefined : new Date(json["expires"]),
@@ -119,7 +119,7 @@ export function TokenModelToJSONTyped(
}
return {
provider: OAuth2ProviderToJSON(value["provider"]),
provider: ProviderToJSON(value["provider"]),
user: UserToJSON(value["user"]),
expires: value["expires"] == null ? value["expires"] : value["expires"].toISOString(),
scope: value["scope"],

View File

@@ -60,7 +60,7 @@ export const LogLevels = /** @type {Level[]} */ (Object.keys(LogLevelLabel));
/**
* @callback LoggerFactory
* @param {string | null} [prefix]
* @param {...string} args
* @param {...string[]} args
* @returns {Logger}
*/
@@ -207,7 +207,7 @@ export function pinoLight(options) {
* Creates a logger with the given prefix.
*
* @param {string} [prefix]
* @param {...string} args
* @param {...string[]} args
* @returns {Logger}
*
*/

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/logger-js",
"version": "1.1.1",
"version": "1.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/logger-js",
"version": "1.1.1",
"version": "1.1.2",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.39.3",
@@ -68,7 +68,7 @@
},
"../tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.8",
"version": "1.0.9",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/logger-js",
"version": "1.1.1",
"version": "1.1.2",
"description": "Pino-based logger for authentik",
"license": "MIT",
"repository": {

View File

@@ -57,7 +57,7 @@ dependencies = [
"pyyaml==6.0.3",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",
"sentry-sdk==2.58.0",
"sentry-sdk==2.59.0",
"service-identity==24.2.0",
"setproctitle==1.3.7",
"structlog==25.5.0",

View File

@@ -5386,6 +5386,8 @@ paths:
using this object
tags:
- endpoints
security:
- {}
responses:
'200':
content:
@@ -5430,6 +5432,8 @@ paths:
using this object
tags:
- endpoints
security:
- {}
responses:
'200':
content:
@@ -5453,6 +5457,8 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/DeviceFactsRequest'
security:
- {}
responses:
'204':
description: Successfully checked in
@@ -5473,6 +5479,8 @@ paths:
schema:
$ref: '#/components/schemas/EnrollRequest'
required: true
security:
- {}
responses:
'200':
content:
@@ -39078,7 +39086,7 @@ components:
readOnly: true
title: ID
provider:
$ref: '#/components/schemas/OAuth2Provider'
$ref: '#/components/schemas/Provider'
user:
$ref: '#/components/schemas/User'
is_expired:
@@ -57253,7 +57261,7 @@ components:
readOnly: true
title: ID
provider:
$ref: '#/components/schemas/OAuth2Provider'
$ref: '#/components/schemas/Provider'
user:
$ref: '#/components/schemas/User'
is_expired:

10
uv.lock generated
View File

@@ -366,7 +366,7 @@ requires-dist = [
{ name = "pyyaml", specifier = "==6.0.3" },
{ name = "requests-oauthlib", specifier = "==2.0.0" },
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
{ name = "sentry-sdk", specifier = "==2.58.0" },
{ name = "sentry-sdk", specifier = "==2.59.0" },
{ name = "service-identity", specifier = "==24.2.0" },
{ name = "setproctitle", specifier = "==1.3.7" },
{ name = "structlog", specifier = "==25.5.0" },
@@ -1106,7 +1106,7 @@ requires-dist = [
{ name = "django", specifier = ">=4.2,<6.0" },
{ name = "django-pgtrigger", specifier = ">=4,<5" },
{ name = "msgpack", specifier = ">=1,<2" },
{ name = "psycopg", extras = ["pool"], specifier = ">=3.3.4,<4" },
{ name = "psycopg", extras = ["pool"], specifier = ">=3,<4" },
{ name = "structlog", specifier = ">=25,<26" },
]
@@ -3332,15 +3332,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.58.0"
version = "2.59.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/b3/fb8291170d0e844173164709fc0fa0c221ed75a5da740c8746f2a83b4eb1/sentry_sdk-2.58.0.tar.gz", hash = "sha256:c1144d947352d54e5b7daa63596d9f848adf684989c06c4f5a659f0c85a18f6f", size = 438764, upload-time = "2026-04-13T17:23:26.265Z" }
sdist = { url = "https://files.pythonhosted.org/packages/65/e0/9bf5e5fc7442b10880f3ec0eff0ef4208b84a099606f343ec4f5445227fb/sentry_sdk-2.59.0.tar.gz", hash = "sha256:cd265808ef8bf3f3edf69b527c0a0b2b6b1322762679e55b8987db2e9584aec1", size = 447331, upload-time = "2026-05-04T12:19:06.538Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/eb/d875669993b762556ae8b2efd86219943b4c0864d22204d622a9aee3052b/sentry_sdk-2.58.0-py2.py3-none-any.whl", hash = "sha256:688d1c704ddecf382ea3326f21a67453d4caa95592d722b7c780a36a9d23109e", size = 460919, upload-time = "2026-04-13T17:23:24.675Z" },
{ url = "https://files.pythonhosted.org/packages/bf/00/b8cc413748fb6383d1582e7cda51314f99743351c462a92dc690d5b5853b/sentry_sdk-2.59.0-py2.py3-none-any.whl", hash = "sha256:abcf65ee9a9d9cdebf9ad369782408ecca9c1c792686ef06ba34f5ab233527fe", size = 468432, upload-time = "2026-05-04T12:19:04.741Z" },
]
[[package]]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 144.29"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><path class="cls-1" d="M106,41.08h25.39v101.2H106v-10.7a50,50,0,0,1-14.92,10.19,41.84,41.84,0,0,1-16.21,3.11q-19.61,0-33.91-15.21T26.64,91.86q0-23.43,13.85-38.41t33.63-15a42.78,42.78,0,0,1,17.09,3.44A46.82,46.82,0,0,1,106,52.24ZM79.29,61.91a25.65,25.65,0,0,0-19.56,8.33q-7.78,8.33-7.79,21.34t7.93,21.58a25.66,25.66,0,0,0,19.51,8.47,26.15,26.15,0,0,0,19.84-8.33q7.88-8.33,7.88-21.81,0-13.2-7.88-21.39T79.29,61.91Z"/><path class="cls-1" d="M168.39,41.08h25.67V89.82q0,14.22,2,19.76a17.24,17.24,0,0,0,6.29,8.61A18.06,18.06,0,0,0,213,121.26a18.6,18.6,0,0,0,10.77-3,17.7,17.7,0,0,0,6.57-8.88q1.59-4.36,1.59-18.7V41.08h25.39V84q0,26.51-4.18,36.27a39.6,39.6,0,0,1-15.07,18.28q-10,6.38-25.3,6.37-16.65,0-26.93-7.44T171.36,116.7q-3-9.21-3-33.49Z"/><path class="cls-1" d="M297.3,3.78h25.39v37.3h15.07V62.93H322.69v79.35H297.3V62.93h-13V41.08h13Z"/><path class="cls-1" d="M362.86,2h25.21v49.3a57.74,57.74,0,0,1,15-9.63,38.56,38.56,0,0,1,15.25-3.21,34.36,34.36,0,0,1,25.39,10.42q8.83,9,8.84,26.51v66.88h-25V97.91q0-17.58-1.68-23.81t-5.71-9.3a16.07,16.07,0,0,0-10-3.07,18.85,18.85,0,0,0-13.26,5.11q-5.53,5.11-7.67,14-1.12,4.56-1.12,20.84v40.65H362.86Z"/><path class="cls-1" d="M589.91,99H508.33q1.77,10.78,9.44,17.16t19.58,6.37a33.86,33.86,0,0,0,24.46-10l21.4,10a50.54,50.54,0,0,1-19.16,16.79q-11.16,5.44-26.51,5.44-23.82,0-38.79-15t-15-37.63q0-23.16,14.93-38.46t37.44-15.3q23.91,0,38.88,15.3t15,40.42Zm-25.4-20a25.48,25.48,0,0,0-9.92-13.77A28.81,28.81,0,0,0,537.4,60a30.42,30.42,0,0,0-18.64,5.95q-5,3.72-9.31,13.12Z"/><path class="cls-1" d="M621.89,41.08h25.39V51.45q8.64-7.29,15.65-10.13a37.82,37.82,0,0,1,14.35-2.85A34.77,34.77,0,0,1,702.83,49q8.82,8.94,8.82,26.42v66.88H686.54V98q0-18.12-1.63-24.06a16.44,16.44,0,0,0-5.66-9.06,15.8,15.8,0,0,0-10-3.11,18.73,18.73,0,0,0-13.23,5.15Q650.53,72,648.4,81.14q-1.12,4.74-1.12,20.54v40.6H621.89Z"/><path class="cls-1" d="M750.71,3.78H776.1v37.3h15.07V62.93H776.1v79.35H750.71V62.93h-13V41.08h13Z"/><path class="cls-1" d="M826.09-.6a15.55,15.55,0,0,1,11.45,4.84A16.08,16.08,0,0,1,842.31,16a15.87,15.87,0,0,1-4.72,11.58,15.34,15.34,0,0,1-11.32,4.79,15.6,15.6,0,0,1-11.55-4.88A16.35,16.35,0,0,1,810,15.59a15.57,15.57,0,0,1,4.73-11.44A15.53,15.53,0,0,1,826.09-.6Z"/><rect class="cls-1" x="813.39" y="41.08" width="25.39" height="101.2"/><path class="cls-1" d="M873.47,2h25.39V82.8l37.39-41.72h31.89l-43.59,48.5,48.81,52.7H941.83l-43-46.64v46.64H873.47Z"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3064.87 487.37"><defs><symbol id="a" viewBox="0 0 2865.3 437.72"><g style="isolation:isolate;"><path d="M238.73,125.38h76.4v304.5h-76.4v-32.18c-14.91,14.18-29.87,24.4-44.87,30.65-15,6.25-31.26,9.37-48.78,9.37-39.32,0-73.33-15.25-102.04-45.76C14.35,361.45,0,323.53,0,278.19s13.89-85.54,41.65-115.58c27.77-30.04,61.5-45.06,101.19-45.06,18.26,0,35.4,3.45,51.43,10.35,16.03,6.91,30.84,17.26,44.45,31.07v-33.58ZM158.41,188.07c-23.62,0-43.24,8.35-58.86,25.05-15.62,16.7-23.43,38.11-23.43,64.23s7.95,47.96,23.84,64.93c15.9,16.98,35.47,25.47,58.72,25.47s43.89-8.35,59.69-25.05c15.8-16.7,23.71-38.57,23.71-65.63s-7.9-47.95-23.71-64.37c-15.81-16.42-35.8-24.63-59.97-24.63Z" style="fill:#fd4b2d;"/><path d="M403.16,125.38h77.24v146.65c0,28.55,1.96,48.37,5.89,59.47,3.93,11.1,10.24,19.73,18.94,25.89,8.69,6.16,19.4,9.24,32.12,9.24s23.52-3.03,32.4-9.1c8.88-6.06,15.47-14.97,19.78-26.73,3.18-8.77,4.77-27.52,4.77-56.25V125.38h76.41v129.02c0,53.18-4.2,89.56-12.59,109.15-10.26,23.88-25.38,42.22-45.34,54.99-19.97,12.78-45.34,19.17-76.13,19.17-33.4,0-60.41-7.46-81.02-22.39-20.62-14.92-35.13-35.73-43.52-62.41-5.97-18.47-8.96-52.06-8.96-100.75v-126.78Z" style="fill:#fd4b2d;"/><path d="M796.76,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M999.76,7.84h75.85v148.33c14.93-12.88,29.95-22.53,45.06-28.97,15.11-6.44,30.41-9.65,45.9-9.65,30.23,0,55.7,10.45,76.41,31.34,17.73,18.1,26.59,44.69,26.59,79.76v201.23h-75.29v-133.5c0-35.27-1.68-59.15-5.04-71.65-3.36-12.5-9.09-21.83-17.21-27.99-8.12-6.15-18.15-9.23-30.09-9.23-15.49,0-28.78,5.13-39.88,15.39-11.11,10.26-18.8,24.26-23.09,41.98-2.24,9.14-3.36,30.04-3.36,62.69v122.3h-75.85V7.84Z" style="fill:#fd4b2d;"/><path d="M1688.63,299.74h-245.45c3.54,21.65,13.01,38.86,28.41,51.64,15.39,12.78,35.03,19.17,58.91,19.17,28.55,0,53.08-9.98,73.6-29.95l64.37,30.23c-16.05,22.77-35.26,39.6-57.65,50.52-22.39,10.91-48.98,16.37-79.76,16.37-47.77,0-86.67-15.06-116.71-45.2-30.04-30.13-45.06-67.87-45.06-113.21s14.97-85.03,44.92-115.73c29.95-30.69,67.49-46.04,112.65-46.04,47.95,0,86.95,15.35,116.99,46.04,30.04,30.69,45.06,71.23,45.06,121.61l-.28,14.55ZM1612.22,239.57c-5.05-16.98-15-30.79-29.86-41.42-14.86-10.63-32.1-15.95-51.72-15.95-21.3,0-40,5.98-56.07,17.91-10.09,7.47-19.44,20.62-28.03,39.46h165.68Z" style="fill:#fd4b2d;"/><path d="M1790.6,125.38h76.41v31.21c17.33-14.61,33.02-24.77,47.09-30.48,14.06-5.71,28.46-8.57,43.18-8.57,30.18,0,55.8,10.54,76.85,31.62,17.7,17.91,26.55,44.41,26.55,79.48v201.23h-75.57v-133.35c0-36.34-1.63-60.47-4.89-72.4-3.26-11.93-8.93-21.01-17.03-27.26-8.1-6.24-18.1-9.36-30.01-9.36-15.45,0-28.71,5.17-39.78,15.51-11.08,10.35-18.76,24.65-23.04,42.91-2.24,9.5-3.35,30.1-3.35,61.78v122.16h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2183.92,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M2416.46,0c13.39,0,24.88,4.85,34.46,14.55,9.58,9.7,14.38,21.46,14.38,35.27s-4.75,25.24-14.24,34.84c-9.49,9.61-20.84,14.41-34.04,14.41s-25.16-4.9-34.75-14.69c-9.58-9.79-14.37-21.69-14.37-35.68s4.74-24.91,14.23-34.43c9.49-9.51,20.93-14.27,34.33-14.27ZM2378.26,125.38h76.41v304.5h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2564.75,7.84h76.41v243.09l112.51-125.54h95.96l-131.17,145.94,146.86,158.56h-94.85l-129.3-140.34v140.34h-76.41V7.84Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="2865.3" height="437.72" transform="translate(99.78 24.83)" xlink:href="#a"/></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><rect class="cls-1" x="546.66" y="275.34" width="34.99" height="99.97"/><rect class="cls-1" x="637.66" y="271.13" width="34.99" height="78.19"/><path class="cls-1" d="M127.64,385.31a127.57,127.57,0,0,0-112.13,66.9H74.82c26.27-22.67,64.42-29.28,92,0h62.8C205.11,419.06,168.36,385.31,127.64,385.31Z"/><path class="cls-1" d="M212.39,512.53C130.55,683.65-12.89,537.81,74.82,452.21H15.51C-31,533.33,33.3,642.73,127.64,640.24c73,0,133.2-108.3,133.2-127.46,0-8.47-11.78-34.33-31.2-60.57h-62.8C187.65,471.08,205.81,498.56,212.39,512.53Zm2.17-5h0Z"/><path class="cls-1" d="M999.94,274.11V725.89c0,86.58-70.42,157.06-157.05,157.06H776.22V729.12H457.88V883H391.22c-86.64,0-157.06-70.48-157.06-157.06V583.81H738.87V312.11H495.24V464.76H234.16V274.11a151.29,151.29,0,0,1,1.06-18,154.4,154.4,0,0,1,3.88-21.15c.58-2.23,1.23-4.46,1.88-6.64a13.66,13.66,0,0,1,.52-1.64c.36-1.12.71-2.17,1.06-3.23s.76-2.17,1.18-3.23c.47-1.23.88-2.41,1.35-3.58s1-2.35,1.47-3.53a159,159,0,0,1,14.27-26.49c.06-.06.12-.17.17-.23,1.41-2.06,2.88-4.11,4.41-6.17,1.29-1.7,2.58-3.35,3.88-5,1.52-1.82,3.11-3.7,4.69-5.46s3.12-3.47,4.76-5.11l.18-.18a36.53,36.53,0,0,1,2.64-2.64,159.75,159.75,0,0,1,18.68-15.63c1.76-1.29,3.64-2.52,5.52-3.76,2.11-1.35,4.23-2.64,6.4-3.93,4.11-2.41,8.28-4.64,12.63-6.64,1.35-.64,2.76-1.29,4.11-1.88a152.81,152.81,0,0,1,18.38-6.63c2.41-.71,4.82-1.35,7.29-1.94,1.17-.3,2.35-.59,3.58-.82a158.5,158.5,0,0,1,21.26-3.12l3.12-.17c.52,0,1-.06,1.52-.06,2.35-.12,4.76-.18,7.17-.18H842.89c2.4,0,4.81.06,7.16.18.53,0,1,.06,1.53.06l3.11.17A158.26,158.26,0,0,1,876,120.58c1.24.23,2.41.52,3.59.82,2.46.59,4.87,1.23,7.28,1.94A152.81,152.81,0,0,1,905.2,130c1.35.59,2.76,1.24,4.11,1.88,4.35,2,8.52,4.23,12.63,6.64,2.18,1.29,4.29,2.58,6.4,3.93,1.88,1.24,3.76,2.47,5.52,3.76a157.53,157.53,0,0,1,21.5,18.45c1.65,1.64,3.23,3.34,4.76,5.11s3.17,3.64,4.7,5.46c1.29,1.64,2.58,3.29,3.87,5,1.53,2.06,3,4.11,4.41,6.17.06.06.12.17.18.23a159.71,159.71,0,0,1,14.27,26.49c.47,1.18,1,2.35,1.47,3.53s.88,2.35,1.35,3.58c.41,1.06.82,2.11,1.17,3.23s.71,2.11,1.06,3.23a15.74,15.74,0,0,1,.53,1.64c.64,2.18,1.29,4.41,1.88,6.64a155.92,155.92,0,0,1,3.87,21.15A151.29,151.29,0,0,1,999.94,274.11Z"/><path class="cls-1" d="M973.27,186.59H260.84A157.05,157.05,0,0,1,391.2,117.07H842.9A157.08,157.08,0,0,1,973.27,186.59Z"/><path class="cls-1" d="M998.94,256.1H235.16a155.35,155.35,0,0,1,25.68-69.51H973.27A155.34,155.34,0,0,1,998.94,256.1Z"/><path class="cls-1" d="M1000,274.11v51.51H738.87V312.11H495.24v13.51H234.1V274.11a153.41,153.41,0,0,1,1.06-18H998.94A151.29,151.29,0,0,1,1000,274.11Z"/><rect class="cls-1" x="234.1" y="325.62" width="261.13" height="69.54"/><rect class="cls-1" x="738.87" y="325.62" width="261.13" height="69.54"/><rect class="cls-1" x="234.1" y="395.16" width="261.13" height="69.48"/><rect class="cls-1" x="738.87" y="395.16" width="261.13" height="69.48"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="c" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 1000"><defs><symbol id="a" viewBox="0 0 998.94 763.82"><path d="M829.67,0h-425.28c-93.1,0-169.27,76.17-169.27,169.27v425.28c0,93.1,76.17,169.27,169.27,169.27h50.18v-165.68h324.96v165.68h50.14c93.1,0,169.27-76.17,169.27-169.27V169.27C998.94,76.17,922.77,0,829.67,0ZM755.98,463.53H235.4v-114.49h268.96v-158.97h43.68v94.7h25.61v-94.7h30.88v69.64h25.61v-69.64h30.88v116.35h25.61v-116.35h43.68v158.97h25.69v114.49Z" style="fill:#fd4b2d;"/><g id="b"><path d="M237.36,342.19h-.02c-25.34-34.27-63.32-69.15-105.42-69.15-48.4.03-92.89,26.58-115.91,69.15-48.08,83.85,18.39,196.94,115.91,194.36,75.46,0,137.69-111.95,137.69-131.75,0-8.76-12.18-35.49-32.25-62.61ZM77.32,342.19c27.16-23.43,66.59-30.27,95.1,0h.02c21.51,19.51,40.28,47.91,47.08,62.35-84.6,176.88-232.87,26.13-142.2-62.35Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="998.94" height="763.82" transform="translate(1 117.03)" xlink:href="#a"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 994.71 151.65"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><path class="cls-1" d="M284.72,50.4H305.5v82.84H284.72v-8.76a40.79,40.79,0,0,1-12.21,8.34,34.14,34.14,0,0,1-13.27,2.55q-16.05,0-27.76-12.45T219.77,92q0-19.18,11.33-31.45t27.53-12.26a34.94,34.94,0,0,1,14,2.82,38.32,38.32,0,0,1,12.1,8.45ZM262.87,67.45a21,21,0,0,0-16,6.82q-6.37,6.81-6.38,17.47T247,109.4a21,21,0,0,0,16,6.93,21.42,21.42,0,0,0,16.24-6.81q6.45-6.81,6.45-17.86,0-10.8-6.45-17.51A21.71,21.71,0,0,0,262.87,67.45Z"/><path class="cls-1" d="M335.8,50.4h21V90.29q0,11.65,1.6,16.18a14.16,14.16,0,0,0,5.16,7,14.76,14.76,0,0,0,8.74,2.51,15.25,15.25,0,0,0,8.81-2.48,14.49,14.49,0,0,0,5.38-7.27q1.31-3.57,1.3-15.3V50.4h20.79V85.5q0,21.69-3.43,29.69a32.32,32.32,0,0,1-12.33,15q-8.16,5.22-20.71,5.22-13.64,0-22.05-6.09a32.2,32.2,0,0,1-11.84-17q-2.43-7.55-2.43-27.41Z"/><path class="cls-1" d="M441.32,19.86H462.1V50.4h12.34V68.29H462.1v65H441.32V68.29H430.66V50.4h10.66Z"/><path class="cls-1" d="M495,18.42h20.63V58.77a47.41,47.41,0,0,1,12.26-7.88,31.62,31.62,0,0,1,12.49-2.63,28.13,28.13,0,0,1,20.78,8.53q7.23,7.4,7.24,21.7v54.75H547.9V96.92q0-14.4-1.37-19.49a13.6,13.6,0,0,0-4.68-7.62,13.19,13.19,0,0,0-8.18-2.51,15.43,15.43,0,0,0-10.85,4.19,22.14,22.14,0,0,0-6.28,11.42q-.91,3.72-.92,17v33.28H495Z"/><path class="cls-1" d="M680.84,97.83H614.06a22.25,22.25,0,0,0,7.73,14q6.29,5.22,16,5.21a27.7,27.7,0,0,0,20-8.14l17.51,8.22a41.31,41.31,0,0,1-15.68,13.74q-9.13,4.46-21.7,4.46-19.5,0-31.75-12.3T594,92.27q0-19,12.22-31.48t30.65-12.53q19.56,0,31.82,12.53t12.26,33.08ZM660.05,81.46a20.87,20.87,0,0,0-8.12-11.27,23.61,23.61,0,0,0-14.08-4.34,24.88,24.88,0,0,0-15.25,4.88q-4.11,3-7.62,10.73Z"/><path class="cls-1" d="M707,50.4H727.8v8.49a50.15,50.15,0,0,1,12.81-8.3,31.08,31.08,0,0,1,11.75-2.33,28.44,28.44,0,0,1,20.91,8.61q7.22,7.31,7.22,21.62v54.75H759.93V97q0-14.83-1.33-19.7A13.48,13.48,0,0,0,754,69.85a13,13,0,0,0-8.16-2.55A15.32,15.32,0,0,0,735,71.52a22.6,22.6,0,0,0-6.27,11.67q-.9,3.89-.91,16.81v33.24H707Z"/><path class="cls-1" d="M812.46,19.86h20.79V50.4h12.33V68.29H833.25v65H812.46V68.29H801.8V50.4h10.66Z"/><path class="cls-1" d="M874.16,16.29a12.74,12.74,0,0,1,9.38,3.95,13.18,13.18,0,0,1,3.91,9.6,13,13,0,0,1-3.87,9.48,12.6,12.6,0,0,1-9.27,3.92,12.73,12.73,0,0,1-9.45-4A13.39,13.39,0,0,1,861,29.53a12.78,12.78,0,0,1,3.87-9.36A12.71,12.71,0,0,1,874.16,16.29Z"/><rect class="cls-1" x="863.77" y="50.4" width="20.79" height="82.84"/><path class="cls-1" d="M913,18.42h20.78V84.55L964.34,50.4h26.11L954.76,90.1l40,43.14h-25.8L933.73,95.06v38.18H913Z"/><rect class="cls-1" x="107.1" y="34.93" width="6.37" height="18.2"/><rect class="cls-1" x="123.67" y="34.16" width="6.37" height="14.23"/><path class="cls-1" d="M30.83,55A23.23,23.23,0,0,0,10.41,67.13h10.8C26,63,32.94,61.8,38,67.13H49.39C44.93,61.09,38.24,55,30.83,55Z"/><path class="cls-1" d="M46.25,78.11c-14.89,31.15-41,4.6-25-11H10.41c-8.47,14.76,3.24,34.68,20.42,34.23,13.28,0,24.24-19.72,24.24-23.21,0-1.54-2.14-6.25-5.68-11H38A40.52,40.52,0,0,1,46.25,78.11Zm.4-.91Z"/><path class="cls-1" d="M189.62,34.71V117A28.62,28.62,0,0,1,161,145.54H148.89v-28H90.94v28H78.81A28.62,28.62,0,0,1,50.22,117V91.08h91.87V41.62H97.74V69.41H50.22V34.71a27.43,27.43,0,0,1,.19-3.29,27.09,27.09,0,0,1,.71-3.84c.1-.41.22-.82.34-1.21a2.13,2.13,0,0,1,.09-.3c.07-.21.13-.4.2-.59s.14-.4.21-.59.16-.44.25-.65.18-.43.26-.64a29.35,29.35,0,0,1,2.6-4.82l0-.05c.26-.37.53-.75.81-1.12s.47-.61.7-.91.57-.67.86-1,.56-.63.86-.93l0,0a4.53,4.53,0,0,1,.49-.49,29.23,29.23,0,0,1,3.4-2.84c.32-.24.66-.46,1-.68s.77-.49,1.17-.72a23.78,23.78,0,0,1,2.29-1.21l.75-.34a27.84,27.84,0,0,1,3.35-1.21c.44-.13.88-.24,1.33-.35a6.19,6.19,0,0,1,.65-.15,28.86,28.86,0,0,1,3.87-.57l.56,0h.28c.43,0,.87,0,1.31,0H161c.43,0,.87,0,1.3,0h.28l.56,0a29.25,29.25,0,0,1,3.88.57c.22,0,.43.09.65.15.45.11.88.22,1.32.35a27.23,27.23,0,0,1,3.35,1.21l.75.34a25.19,25.19,0,0,1,2.3,1.21c.39.23.78.47,1.16.72s.69.44,1,.68a29.23,29.23,0,0,1,3.91,3.36q.45.45.87.93c.29.32.57.66.85,1l.71.91c.28.37.54.75.8,1.12l0,.05a28.61,28.61,0,0,1,2.6,4.82l.27.64.24.65c.08.19.15.39.22.59l.19.59c0,.09.06.19.1.3.11.39.23.8.34,1.21a28.56,28.56,0,0,1,.7,3.84A27.42,27.42,0,0,1,189.62,34.71Z"/><path class="cls-1" d="M184.76,18.78H55.07A28.59,28.59,0,0,1,78.8,6.12H161A28.59,28.59,0,0,1,184.76,18.78Z"/><path class="cls-1" d="M189.43,31.43H50.4a28.29,28.29,0,0,1,4.67-12.65H184.76A28.17,28.17,0,0,1,189.43,31.43Z"/><path class="cls-1" d="M189.63,34.71v9.37H142.09V41.62H97.74v2.46H50.21V34.71a27.43,27.43,0,0,1,.19-3.29h139A27.42,27.42,0,0,1,189.63,34.71Z"/><rect class="cls-1" x="50.21" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="142.09" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="50.21" y="56.74" width="47.54" height="12.65"/><rect class="cls-1" x="142.09" y="56.74" width="47.54" height="12.65"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="i" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3767.3 592.89"><defs><symbol id="a" viewBox="0 0 998.94 763.82"><path d="M829.67,0h-425.28c-93.1,0-169.27,76.17-169.27,169.27v425.28c0,93.1,76.17,169.27,169.27,169.27h50.18v-165.68h324.96v165.68h50.14c93.1,0,169.27-76.17,169.27-169.27V169.27C998.94,76.17,922.77,0,829.67,0ZM755.98,463.53H235.4v-114.49h268.96v-158.97h43.68v94.7h25.61v-94.7h30.88v69.64h25.61v-69.64h30.88v116.35h25.61v-116.35h43.68v158.97h25.69v114.49Z" style="fill:#fd4b2d;"/><g id="b"><path d="M237.36,342.19h-.02c-25.34-34.27-63.32-69.15-105.42-69.15-48.4.03-92.89,26.58-115.91,69.15-48.08,83.85,18.39,196.94,115.91,194.36,75.46,0,137.69-111.95,137.69-131.75,0-8.76-12.18-35.49-32.25-62.61ZM77.32,342.19c27.16-23.43,66.59-30.27,95.1,0h.02c21.51,19.51,40.28,47.91,47.08,62.35-84.6,176.88-232.87,26.13-142.2-62.35Z" style="fill:#fd4b2d;"/></g></symbol><symbol id="c" viewBox="0 0 2865.3 437.72"><g style="isolation:isolate;"><path d="M238.73,125.38h76.4v304.5h-76.4v-32.18c-14.91,14.18-29.87,24.4-44.87,30.65-15,6.25-31.26,9.37-48.78,9.37-39.32,0-73.33-15.25-102.04-45.76C14.35,361.45,0,323.53,0,278.19s13.89-85.54,41.65-115.58c27.77-30.04,61.5-45.06,101.19-45.06,18.26,0,35.4,3.45,51.43,10.35,16.03,6.91,30.84,17.26,44.45,31.07v-33.58ZM158.41,188.07c-23.62,0-43.24,8.35-58.86,25.05-15.62,16.7-23.43,38.11-23.43,64.23s7.95,47.96,23.84,64.93c15.9,16.98,35.47,25.47,58.72,25.47s43.89-8.35,59.69-25.05c15.8-16.7,23.71-38.57,23.71-65.63s-7.9-47.95-23.71-64.37c-15.81-16.42-35.8-24.63-59.97-24.63Z" style="fill:#fd4b2d;"/><path d="M403.16,125.38h77.24v146.65c0,28.55,1.96,48.37,5.89,59.47,3.93,11.1,10.24,19.73,18.94,25.89,8.69,6.16,19.4,9.24,32.12,9.24s23.52-3.03,32.4-9.1c8.88-6.06,15.47-14.97,19.78-26.73,3.18-8.77,4.77-27.52,4.77-56.25V125.38h76.41v129.02c0,53.18-4.2,89.56-12.59,109.15-10.26,23.88-25.38,42.22-45.34,54.99-19.97,12.78-45.34,19.17-76.13,19.17-33.4,0-60.41-7.46-81.02-22.39-20.62-14.92-35.13-35.73-43.52-62.41-5.97-18.47-8.96-52.06-8.96-100.75v-126.78Z" style="fill:#fd4b2d;"/><path d="M796.76,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M999.76,7.84h75.85v148.33c14.93-12.88,29.95-22.53,45.06-28.97,15.11-6.44,30.41-9.65,45.9-9.65,30.23,0,55.7,10.45,76.41,31.34,17.73,18.1,26.59,44.69,26.59,79.76v201.23h-75.29v-133.5c0-35.27-1.68-59.15-5.04-71.65-3.36-12.5-9.09-21.83-17.21-27.99-8.12-6.15-18.15-9.23-30.09-9.23-15.49,0-28.78,5.13-39.88,15.39-11.11,10.26-18.8,24.26-23.09,41.98-2.24,9.14-3.36,30.04-3.36,62.69v122.3h-75.85V7.84Z" style="fill:#fd4b2d;"/><path d="M1688.63,299.74h-245.45c3.54,21.65,13.01,38.86,28.41,51.64,15.39,12.78,35.03,19.17,58.91,19.17,28.55,0,53.08-9.98,73.6-29.95l64.37,30.23c-16.05,22.77-35.26,39.6-57.65,50.52-22.39,10.91-48.98,16.37-79.76,16.37-47.77,0-86.67-15.06-116.71-45.2-30.04-30.13-45.06-67.87-45.06-113.21s14.97-85.03,44.92-115.73c29.95-30.69,67.49-46.04,112.65-46.04,47.95,0,86.95,15.35,116.99,46.04,30.04,30.69,45.06,71.23,45.06,121.61l-.28,14.55ZM1612.22,239.57c-5.05-16.98-15-30.79-29.86-41.42-14.86-10.63-32.1-15.95-51.72-15.95-21.3,0-40,5.98-56.07,17.91-10.09,7.47-19.44,20.62-28.03,39.46h165.68Z" style="fill:#fd4b2d;"/><path d="M1790.6,125.38h76.41v31.21c17.33-14.61,33.02-24.77,47.09-30.48,14.06-5.71,28.46-8.57,43.18-8.57,30.18,0,55.8,10.54,76.85,31.62,17.7,17.91,26.55,44.41,26.55,79.48v201.23h-75.57v-133.35c0-36.34-1.63-60.47-4.89-72.4-3.26-11.93-8.93-21.01-17.03-27.26-8.1-6.24-18.1-9.36-30.01-9.36-15.45,0-28.71,5.17-39.78,15.51-11.08,10.35-18.76,24.65-23.04,42.91-2.24,9.5-3.35,30.1-3.35,61.78v122.16h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2183.92,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M2416.46,0c13.39,0,24.88,4.85,34.46,14.55,9.58,9.7,14.38,21.46,14.38,35.27s-4.75,25.24-14.24,34.84c-9.49,9.61-20.84,14.41-34.04,14.41s-25.16-4.9-34.75-14.69c-9.58-9.79-14.37-21.69-14.37-35.68s4.74-24.91,14.23-34.43c9.49-9.51,20.93-14.27,34.33-14.27ZM2378.26,125.38h76.41v304.5h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2564.75,7.84h76.41v243.09l112.51-125.54h95.96l-131.17,145.94,146.86,158.56h-94.85l-129.3-140.34v140.34h-76.41V7.84Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="998.94" height="763.82" transform="translate(28.54 36.14) scale(.68)" xlink:href="#a"/><use width="2865.3" height="437.72" transform="translate(802.22 67.81)" xlink:href="#c"/></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -63,7 +63,7 @@ const LogLevelColors = /** @type {const} */ ({
* Creates a logger with the given prefix.
*
* @param {string} [prefix]
* @param {...string} args
* @param {...string[]} args
* @returns {Logger}
*
*/

2008
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -127,14 +127,15 @@
"@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5",
"@types/node": "^25.6.0",
"@types/node": "^25.6.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"@typescript-eslint/utils": "^8.57.2",
"@vitest/browser": "^4.1.5",
"@vitest/browser-playwright": "^4.0.15",
"@typescript/native-preview": "^7.0.0-dev.20260421.2",
"@vitest/browser": "^4.1.6",
"@vitest/browser-playwright": "^4.1.6",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"change-case": "^5.4.4",
@@ -151,7 +152,7 @@
"eslint-plugin-lit": "^2.2.1",
"eslint-plugin-wc": "^3.1.0",
"fuse.js": "^7.3.0",
"globals": "^17.5.0",
"globals": "^17.6.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"knip": "^6.11.0",
@@ -161,12 +162,12 @@
"lit-element": "^4.2.2",
"lit-html": "^3.3.2",
"md-front-matter": "^1.0.4",
"mermaid": "^11.14.0",
"mermaid": "^11.15.0",
"node-domexception": "^2025.11.0",
"npm-run-all": "^4.1.5",
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"playwright": "^1.58.2",
"playwright": "^1.60.0",
"prettier": "^3.8.3",
"prettier-plugin-packagejson": "^3.0.2",
"pseudolocale": "^2.2.0",
@@ -183,6 +184,7 @@
"remark-mdx-frontmatter": "^5.2.0",
"storybook": "^10.2.1",
"style-mod": "^4.1.3",
"stylelint": "^17.11.0",
"trusted-types": "^2.0.0",
"ts-pattern": "^5.9.0",
"turnstile-types": "^1.2.3",
@@ -190,8 +192,8 @@
"typescript": "^6.0.3",
"typescript-eslint": "^8.57.2",
"unist-util-visit": "^5.1.0",
"vite": "^8.0.10",
"vitest": "^4.1.1",
"vite": "^8.0.12",
"vitest": "^4.1.6",
"webcomponent-qr-code": "^1.3.0",
"wireit": "^0.14.12",
"yaml": "^2.8.4"
@@ -202,8 +204,7 @@
"@esbuild/linux-x64": "^0.28.0",
"@rollup/rollup-darwin-arm64": "^4.57.1",
"@rollup/rollup-linux-arm64-gnu": "^4.57.1",
"@rollup/rollup-linux-x64-gnu": "^4.57.1",
"chromedriver": "^147.0.4"
"@rollup/rollup-linux-x64-gnu": "^4.57.1"
},
"workspaces": [
"./packages/*"
@@ -260,10 +261,7 @@
"command": "lit-analyzer src"
},
"lint:types": {
"command": "tsc -p .",
"env": {
"NODE_OPTIONS": "--max_old_space_size=8192"
},
"command": "tsgo -p .",
"dependencies": [
"build-locales"
]
@@ -292,10 +290,7 @@
}
},
"tsc": {
"command": "tsc -p .",
"env": {
"NODE_OPTIONS": "--max_old_space_size=8192"
},
"command": "tsgo -p .",
"dependencies": [
"build-locales"
]
@@ -332,7 +327,8 @@
"typescript": "$typescript"
},
"@mrmarble/djangoql-completion": {
"lex": "$lex"
"lex": "$lex",
"lodash": "^4.18.1"
},
"@typescript-eslint/eslint-plugin": {
"typescript": "$typescript"
@@ -340,6 +336,9 @@
"@typescript-eslint/parser": {
"typescript": "$typescript"
},
"@typescript-eslint/typescript-estree": {
"typescript": "$typescript"
},
"@typescript-eslint/utils": {
"typescript": "$typescript"
},
@@ -354,6 +353,9 @@
},
"typescript-eslint": {
"typescript": "$typescript"
},
"wireit": {
"brace-expansion": "^1.1.14"
}
}
}

View File

@@ -45,7 +45,7 @@
},
"dependencies": {
"@goauthentik/tsconfig": "^1.0.9",
"@types/node": "^25.6.0",
"@types/node": "^25.6.2",
"@types/semver": "^7.7.1",
"semver": "^7.7.4",
"typescript": "^6.0.3"

View File

@@ -25,7 +25,7 @@
*
* @callback LexerAction
* @this {Lexer}
* @param {...string} match
* @param {...string[]} match
* @returns {Token | Token[] | null | void}
*/

View File

@@ -29,15 +29,16 @@ import { renderDialog } from "#elements/dialogs";
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
import { WithNotifications } from "#elements/mixins/notifications";
import { canAccessAdmin, WithSession } from "#elements/mixins/session";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { navigate } from "#elements/router/RouterOutlet";
import { SlottedTemplateResult } from "#elements/types";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import {
DrawerState,
persistDrawerParams,
readDrawerParams,
renderNotificationDrawerPanel,
} from "#elements/notifications/utils";
import { navigate } from "#elements/router/RouterOutlet";
import { SlottedTemplateResult } from "#elements/types";
} from "#components/notifications/utils";
import Styles from "#admin/ak-interface-admin.css";
import { ROUTES } from "#admin/Routes";

View File

@@ -54,7 +54,7 @@ import { ToggleUserActivationButton } from "#admin/users/UserActiveForm";
import { UserForm } from "#admin/users/UserForm";
import { UserImpersonateForm } from "#admin/users/UserImpersonateForm";
import { CapabilitiesEnum, CoreApi, ModelEnum, User } from "@goauthentik/api";
import { CapabilitiesEnum, CoreApi, ModelEnum, User, UserTypeEnum } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
import { css, html, PropertyValues, TemplateResult } from "lit";
@@ -192,7 +192,10 @@ export class UserViewPage extends WithLicenseSummary(
protected renderActionButtons(user: User) {
const showImpersonate =
this.can(CapabilitiesEnum.CanImpersonate) && user.pk !== this.currentUser?.pk;
const showLockdown = this.hasEnterpriseLicense && user.pk !== this.currentUser?.pk;
const showLockdown =
this.hasEnterpriseLicense &&
user.pk !== this.currentUser?.pk &&
user.type !== UserTypeEnum.InternalServiceAccount;
const displayName = formatUserDisplayName(user);

View File

@@ -10,10 +10,10 @@ import { formatUserDisplayName } from "#common/users";
import { AKElement } from "#elements/Base";
import { WithNotifications } from "#elements/mixins/notifications";
import { WithSession } from "#elements/mixins/session";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { isDefaultAvatar } from "#elements/utils/images";
import Styles from "#components/ak-nav-button.css";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import { CoreApi } from "@goauthentik/api";

View File

@@ -5,7 +5,8 @@ import { globalAK } from "#common/global";
import { AKElement } from "#elements/Base";
import { listen } from "#elements/decorators/listen";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import { msg } from "@lit/localize";
import { css, CSSResult, html, TemplateResult } from "lit";
@@ -80,14 +81,9 @@ export class APIDrawer extends AKElement {
@listen(AKRequestPostEvent, { target: window })
protected enqueueRequest = ({ requestInfo }: AKRequestPostEvent) => {
this.requests.push(requestInfo);
this.requests.sort((a, b) => a.time - b.time).reverse();
if (this.requests.length > 50) {
this.requests.shift();
}
this.requestUpdate();
this.requests = [requestInfo, ...this.requests]
.toSorted((a, b) => b.time - a.time)
.slice(0, 50);
};
render(): TemplateResult {

View File

@@ -10,10 +10,11 @@ import { formatElapsedTime } from "#common/temporal";
import { AKElement } from "#elements/Base";
import { WithNotifications } from "#elements/mixins/notifications";
import { WithSession } from "#elements/mixins/session";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { SlottedTemplateResult } from "#elements/types";
import { ifPresent } from "#elements/utils/attributes";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import { Notification } from "@goauthentik/api";
import { msg, str } from "@lit/localize";

View File

@@ -1,4 +1,4 @@
import { DrawerState, readDrawerParams } from "#elements/notifications/utils";
import { DrawerState, readDrawerParams } from "#components/notifications/utils";
/**
* Event dispatched when the state of the interface drawers changes.

View File

@@ -2,8 +2,8 @@
* @file Notification drawer utilities.
*/
import "#elements/notifications/APIDrawer";
import "#elements/notifications/NotificationDrawer";
import "#components/notifications/APIDrawer";
import "#components/notifications/NotificationDrawer";
import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";

View File

@@ -1,32 +1,73 @@
import Style from "./ak-drawer.css";
import AKDrawer from "./ak-drawer.styles";
import { DrawerResizeController } from "./drawerResizeController";
import { AKElement } from "#elements/Base";
import { classList } from "#elements/directives/class-list";
import { html } from "lit";
import { html, LitElement, nothing, PropertyValues } from "lit";
import { property } from "lit/decorators.js";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
export class DrawerExpandRequest extends Event {
static readonly eventName = "ak-drawer-expand-request";
expanded: boolean | null = null;
export class Drawer extends AKElement {
static readonly styles = [PFDrawer, Style];
constructor(expanded: boolean | null = null) {
super(DrawerExpandRequest.eventName, { bubbles: true, composed: true });
this.expanded = expanded;
}
}
export class AkDrawer extends LitElement {
static readonly styles = [AKDrawer];
@property({ type: Boolean })
public resizable = false;
@property({ type: Boolean, reflect: true })
public open = false;
public expanded = false;
render() {
const open = [(this.open && "pf-m-expanded") || "pf-m-collapsed"];
@property({ type: Boolean, reflect: true })
public resizing = false;
@property({ type: String, reflect: true })
public width = "33";
private resize = new DrawerResizeController(this);
onDrawerRequest = (ev: DrawerExpandRequest) => {
ev.stopPropagation();
this.expanded = ev.expanded === null ? !this.expanded : ev.expanded;
};
constructor() {
super();
this.addEventListener(DrawerExpandRequest.eventName, this.onDrawerRequest);
}
public override render() {
return html`
<div class="pf-c-page__drawer">
<div class="pf-c-drawer ${classList(open)}" id="flow-drawer">
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<slot></slot>
</div>
<div class="ak-v2-c-drawer" part="drawer">
<div class="ak-v2-c-drawer__main" part="drawer-main">
<div class="ak-v2-c-drawer__content" part="drawer-content">
<div class="ak-v2-c-drawer__body" part="drawer-body">
<slot></slot>
</div>
<div class="pf-c-drawer__panel pf-m-width-33">
</div>
<div class="ak-v2-c-drawer__panel" part="drawer-panel">
${this.resizable
? html` <div
class="ak-v2-c-drawer__splitter"
part="drawer-splitter"
@mousedown=${this.resize.handleMouseDown}
@keydown=${this.resize.handleKeyDown}
@touchstart=${this.resize.handleTouchStart}
role="separator"
tabindex="0"
>
<div
class="ak-v2-c-drawer__splitter-handle"
aria-hidden="true"
></div>
</div>`
: nothing}
<div class="ak-v2-c-drawer__panel-main" part="drawer-panel-main">
<slot name="panel"></slot>
</div>
</div>
@@ -34,4 +75,26 @@ export class Drawer extends AKElement {
</div>
`;
}
public override updated(changed: PropertyValues<this>) {
super.updated(changed);
// Simulate the behavior of summary/details, another disclosure pattern.
const expanded = changed.get("expanded");
if (expanded !== undefined) {
const expandedMsg = (i: boolean) => (i ? "open" : "closed");
this.dispatchEvent(
new ToggleEvent("toggle", {
newState: expandedMsg(this.expanded),
oldState: expandedMsg(expanded),
}),
);
}
}
}
declare global {
interface GlobalEventHandlersEventMap {
[DrawerExpandRequest.eventName]: DrawerExpandRequest;
}
}

View File

@@ -1,40 +0,0 @@
slot {
display: content;
}
[data-theme="dark"] {
--pf-c-drawer__panel--BackgroundColor: var(--ak-dark-background);
}
.pf-c-drawer {
/* TODO: Revisit this after native <dialog> modals are implemented. */
--pf-c-drawer__content--ZIndex: auto;
}
.pf-c-drawer__body {
display: flex;
flex-flow: column;
}
.pf-c-drawer__content {
--pf-c-drawer__content--BackgroundColor: transparent;
}
.pf-c-drawer {
.pf-c-drawer__panel {
background-color: var(--pf-c-drawer__panel--BackgroundColor);
transition-behavior: allow-discrete;
gap: var(--pf-global--spacer--sm);
@media (width > 768px) {
flex-flow: row;
.pf-c-drawer__panel_content {
flex: 1 1 auto;
max-width: 33dvw;
}
}
}
}

View File

@@ -0,0 +1,141 @@
/* ----------- CSS Custom Properties for DRAWER --------------------------- */
:root {
--ak-v2-c-drawer__content--FlexBasis: 100%;
--ak-v2-c-drawer__content--BackgroundColor: var(--ak-v2-global--ContentSurface);
--ak-v2-c-drawer__content--ZIndex: var(--ak-v2-global--ZIndex--xs, auto);
--ak-v2-c-drawer__panel--MinWidth: 50%;
--ak-v2-c-drawer__panel--MaxHeight: auto;
--ak-v2-c-drawer__panel--ZIndex: var(--ak-v2-global--ZIndex--sm);
--ak-v2-c-drawer__panel--BackgroundColor: var(--ak-v2-global--ContentSurface);
--ak-v2-c-drawer__panel--TransitionDuration: var(--ak-v2-global--TransitionDuration);
--ak-v2-c-drawer__panel--TransitionProperty: margin, transform, box-shadow, flex-basis;
--ak-v2-c-drawer__panel--FlexBasis: 100%;
--ak-v2-c-drawer__panel--md--FlexBasis--min: 1.5rem;
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
--ak-v2-c-drawer__panel--md--FlexBasis--max: 100%;
--ak-v2-c-drawer__panel--xl--MinWidth: 28.125rem;
--ak-v2-c-drawer__panel--xl--FlexBasis: 28.125rem;
--ak-v2-c-drawer--m-panel-bottom__panel--md--MinHeight: 50%;
--ak-v2-c-drawer--m-panel-bottom__panel--xl--MinHeight: 18.75rem;
--ak-v2-c-drawer--m-panel-bottom__panel--xl--FlexBasis: 18.75rem;
--ak-v2-c-drawer__panel--m-resizable--FlexDirection: row;
--ak-v2-c-drawer__panel--m-resizable--md--FlexBasis--min: var(
--ak-v2-c-drawer__splitter--m-vertical--Width
);
--ak-v2-c-drawer__panel--m-resizable--MinWidth: 1.5rem;
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--FlexDirection: column;
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--md--FlexBasis--min: 1.5rem;
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--MinHeight: 1.5rem;
--ak-v2-c-drawer__splitter--Height: 0.5625rem;
--ak-v2-c-drawer__splitter--Width: 100%;
--ak-v2-c-drawer__splitter--BackgroundColor: var(--ak-v2-global--ContentSurface);
--ak-v2-c-drawer__splitter--Cursor: row-resize;
--ak-v2-c-drawer__splitter--m-vertical--Height: 100%;
--ak-v2-c-drawer__splitter--m-vertical--Width: 0.5625rem;
--ak-v2-c-drawer__splitter--m-vertical--Cursor: col-resize;
--ak-v2-c-drawer--m-inline__splitter--focus--OutlineOffset: -0.0625rem;
--ak-v2-c-drawer__splitter--after--BorderColor: var(--ak-v2-global--BorderColor--100);
--ak-v2-c-drawer__splitter--after--border-width--base: var(--ak-v2-global--BorderWidth--sm);
--ak-v2-c-drawer__splitter--after--BorderTopWidth: 0;
--ak-v2-c-drawer__splitter--after--BorderRightWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer__splitter--after--BorderBottomWidth: 0;
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: 0;
--ak-v2-c-drawer--m-panel-left__splitter--after--BorderLeftWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer--m-panel-bottom__splitter--after--BorderBottomWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer--m-inline__splitter--m-vertical--Width: 0.625rem;
--ak-v2-c-drawer--m-inline__splitter-handle--Left: 50%;
--ak-v2-c-drawer--m-inline__splitter--after--BorderRightWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer--m-inline__splitter--after--BorderLeftWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--Height: 0.625rem;
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter-handle--Top: 50%;
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--after--BorderTopWidth: var(
--ak-v2-c-drawer__splitter--after--border-width--base
);
--ak-v2-c-drawer__splitter-handle--Top: 50%;
--ak-v2-c-drawer__splitter-handle--Left: calc(
50% - var(--ak-v2-c-drawer__splitter--after--border-width--base)
);
--ak-v2-c-drawer--m-panel-left__splitter-handle--Left: 50%;
--ak-v2-c-drawer--m-panel-bottom__splitter-handle--Top: calc(
50% - var(--ak-v2-c-drawer__splitter--after--border-width--base)
);
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(--ak-v2-global--Color--200);
--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth: var(--ak-v2-global--BorderWidth--sm);
--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth: 0;
--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth: var(
--ak-v2-global--BorderWidth--sm
);
--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth: 0;
--ak-v2-c-drawer__splitter--hover__splitter-handle--after--BorderColor: var(
--ak-v2-global--Color--100
);
--ak-v2-c-drawer__splitter--focus__splitter-handle--after--BorderColor: var(
--ak-v2-global--Color--100
);
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderTopWidth: 0;
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderRightWidth: var(
--ak-v2-global--BorderWidth--sm
);
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderBottomWidth: 0;
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderLeftWidth: var(
--ak-v2-global--BorderWidth--sm
);
--ak-v2-c-drawer__splitter-handle--after--Width: 0.75rem;
--ak-v2-c-drawer__splitter-handle--after--Height: 0.25rem;
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Width: 0.25rem;
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Height: 0.75rem;
}
@media screen and (min-width: 1200px) {
:root {
--ak-v2-c-drawer__panel--MinWidth: var(--ak-v2-c-drawer__panel--xl--MinWidth);
}
}
:root {
--ak-v2-c-drawer__panel--BoxShadow: none;
--ak-v2-c-drawer--m-expanded--m-panel-bottom__panel--BoxShadow: var(
--ak-v2-global--BoxShadow--lg-top
);
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(--ak-v2-global--BoxShadow--lg-left);
}
:root {
--ak-v2-c-drawer--m-expanded--m-panel-left__panel--BoxShadow: var(
--ak-v2-global--BoxShadow--lg-right
);
}
:root {
--ak-v2-c-drawer__panel--after--Width: var(--ak-v2-global--BorderWidth--sm);
--ak-v2-c-drawer--m-panel-bottom__panel--after--Height: var(--ak-v2-global--BorderWidth--sm);
--ak-v2-c-drawer__panel--after--BackgroundColor: transparent;
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor: var(
--ak-v2-global--BorderColor--100
);
--ak-v2-c-drawer--m-inline__panel--PaddingLeft: var(--ak-v2-c-drawer__panel--after--Width);
--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight: var(
--ak-v2-c-drawer__panel--after--Width
);
--ak-v2-c-drawer--m-panel-bottom--m-inline__panel--PaddingTop: var(
--ak-v2-c-drawer__panel--after--Width
);
}
html[data-theme="dark"],
.ak-t-dark,
.pf-t-dark {
--ak-v2-c-drawer__panel--BackgroundColor: var(--ak-v2-global--ContentSurface);
--ak-v2-c-drawer__splitter--BackgroundColor: transparent;
}

View File

@@ -0,0 +1,151 @@
import "./ak-drawer";
import { DrawerExpandRequest } from "./ak-drawer.component";
import type { Meta, StoryObj } from "@storybook/web-components-vite";
import { html, TemplateResult } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
const toggle = (e: Event) => {
const button = e.target as HTMLButtonElement;
button.dispatchEvent(new DrawerExpandRequest());
};
const contentBlock = html`
<div style="padding: 1rem;">
<h2>Main Content</h2>
<p><button @click=${toggle}>Toggle Drawer</button></p>
<p>
This is the drawer's main: fill it by inserting slotted content without a slot name.
This is the part that stays visible most of the time.
</p>
<p>
Macaroon lollipop croissant sweet biscuit croissant chocolate cake. Cake cake pastry
soufflé pudding. Tiramisu lollipop chocolate cake toffee oat cake muffin topping tootsie
roll. Carrot cake bonbon chupa chups sugar plum fruitcake. Brownie sweet halvah oat cake
cheesecake topping chocolate. Wafer macaroon topping lollipop powder cupcake sugar plum
donut. Muffin wafer icing danish jelly-o bonbon. Powder shortbread brownie caramels
tootsie roll dragée liquorice. Cake lemon drops powder danish toffee.
</p>
</div>
`;
const panelBlock = html`
<style>
[slot="panel"] {
padding: 1rem;
background-color: var(--pf-v5-global--BackgroundColor--200, #f0f0f0);
}
</style>
<div slot="panel">
<h3>Panel Content</h3>
<p>This is the side panel. This is where you put the secondary information.</p>
<ul>
<li>
Seasonal, steamed, con panna and rich ut aged cup decaffeinated single origin con
panna bar
</li>
<li>Skinny mazagran whipped, black iced beans carajillo eu cream</li>
<li>Americano pumpkin spice milk ristretto caffeine single shot</li>
</ul>
<p><button @click=${toggle}>Toggle Drawer</button></p>
</div>
`;
interface DrawerProps {
expanded?: boolean;
inline?: boolean;
static?: boolean;
resizable?: boolean;
width?: string;
position?: string;
content?: TemplateResult;
panel?: TemplateResult;
}
const meta = {
title: "Components/Drawer",
component: "ak-drawer",
tags: ["autodocs"],
decorators: [
(story) =>
html`<div style="min-height: 400px; border: 1px solid #d2d2d2; overflow: hidden;">
${story()}
</div>`,
],
argTypes: {
expanded: { control: "boolean" },
position: {
control: { type: "select" },
options: ["right", "left", "bottom"],
},
inline: { control: "boolean" },
static: { control: "boolean" },
resizable: { control: "boolean" },
width: {
control: { type: "select" },
options: ["25", "33", "50", "66", "75", "100"],
},
},
} satisfies Meta;
export default meta;
type Story = StoryObj;
const Template: Story = {
args: {
expanded: false,
inline: false,
static: false,
resizable: false,
width: undefined,
position: undefined,
content: contentBlock,
panel: panelBlock,
},
render: (args) => {
return html` <ak-drawer
?expanded=${args.expanded}
?inline=${args.inline}
?resizable=${args.resizable}
position=${ifDefined(args.position)}
width=${ifDefined(args.width)}
>
${args.content} ${args.panel}
</ak-drawer>`;
},
};
export const Default: Story = {
render: () => html` <ak-drawer> ${contentBlock} ${panelBlock} </ak-drawer> `,
};
export const story = (args: DrawerProps = {}, name?: string): Story => ({
...Template,
...(name ? { name } : {}),
args: {
...Template.args,
...args,
},
});
export const Expanded: Story = story({ expanded: true });
export const PanelLeft: Story = story({ expanded: true, position: "left" });
export const PanelBottom = story({ expanded: true, position: "bottom" });
export const Inline = story({ expanded: true, inline: true });
export const Static = story({ expanded: true, static: true });
export const Resizable = story({ expanded: true, resizable: true });
export const ResizableLeft = story({ expanded: true, resizable: true, position: "left" });
export const ResizableBottom = story({ expanded: true, resizable: true, position: "bottom" });
export const CustomWidth = story({ expanded: true, width: "33" });
export const ResponsiveWidth = story({ expanded: true, width: "75-on-xl" });

View File

@@ -0,0 +1,914 @@
import { css } from "lit";
export const styles = css`
:host {
display: flex;
flex-direction: column;
height: 100%;
}
.ak-v2-c-drawer {
display: flex;
flex-direction: column;
height: 100%;
overflow-x: hidden;
}
:host([position="bottom"]) .ak-v2-c-drawer {
overflow-x: auto;
overflow-y: hidden;
}
slot {
display: contents;
}
:host([inline]:not([no-border])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([inline]:not([resizable])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static]:not([no-border])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static]:not([resizable])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
padding-inline-start: var(--ak-v2-c-drawer--m-inline__panel--PaddingLeft);
}
:host([position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
order: 0;
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([position="left"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
order: 1;
}
:host([position="bottom"]) .ak-v2-c-drawer__main {
flex-direction: column;
}
:host(:not([inline], [static])) .ak-v2-c-drawer__main {
position: relative;
}
:host(:not([inline], [static])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
position: absolute;
inset-block-start: 0;
inset-block-end: 0;
inset-inline-end: 0;
max-width: var(--ak-v2-c-drawer__panel--FlexBasis);
transform: translateX(100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host(:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([expanded]:not([inline], [static])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([position="left"]:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
inset-inline-end: auto;
inset-inline-start: 0;
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([position="left"]:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([expanded][position="left"]:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([position="bottom"]:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
inset-inline-end: 0;
inset-inline-start: 0;
inset-block-start: auto;
inset-block-end: 0;
max-width: none;
max-height: var(--ak-v2-c-drawer__panel--FlexBasis);
transform: translateY(100%);
}
:host([position="bottom"][expanded]:not([inline], [static]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateY(0);
}
:host([class*="pf-m-resizing"]) {
--ak-v2-c-drawer__panel--TransitionProperty: none;
pointer-events: none;
}
:host([class*="pf-m-resizing"]) .ak-v2-c-drawer__splitter {
pointer-events: auto;
}
.ak-v2-c-drawer__main {
display: flex;
flex: 1;
overflow: hidden;
}
.ak-v2-c-drawer__content,
.ak-v2-c-drawer__panel,
.ak-v2-c-drawer__panel-main {
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: auto;
--ak-v2-c-drawer__content--BackgroundColor: transparent;
}
.ak-v2-c-drawer__content {
z-index: var(--ak-v2-c-drawer__content--ZIndex);
flex-basis: var(--ak-v2-c-drawer__content--FlexBasis);
order: 0;
background-color: var(--ak-v2-c-drawer__content--BackgroundColor);
}
.ak-v2-c-drawer__panel {
position: relative;
z-index: var(--ak-v2-c-drawer__panel--ZIndex);
flex-basis: var(--ak-v2-c-drawer__panel--FlexBasis);
order: 1;
max-height: var(--ak-v2-c-drawer__panel--MaxHeight);
gap: var(--ak-v2-global--spacer--sm);
overflow: auto;
background-color: var(--ak-v2-c-drawer__panel--BackgroundColor);
box-shadow: var(--ak-v2-c-drawer__panel--BoxShadow);
transition-duration: var(--ak-v2-c-drawer__panel--TransitionDuration);
transition-property: var(--ak-v2-c-drawer__panel--TransitionProperty);
transition-behavior: allow-discrete;
-webkit-overflow-scrolling: touch;
}
.ak-v2-c-drawer__panel::after {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
width: var(--ak-v2-c-drawer__panel--after--Width);
height: 100%;
content: "";
background-color: var(--ak-v2-c-drawer__panel--after--BackgroundColor);
}
@media screen and (min-width: 768px) {
.ak-v2-c-drawer__panel {
--ak-v2-c-drawer__panel--FlexBasis: max(
var(--ak-v2-c-drawer__panel--md--FlexBasis--min),
min(
var(--ak-v2-c-drawer__panel--md--FlexBasis),
var(--ak-v2-c-drawer__panel--md--FlexBasis--max)
)
);
}
}
@media screen and (min-width: 1200px) {
:host(:not([width])) .ak-v2-c-drawer__panel {
--ak-v2-c-drawer__panel--md--FlexBasis: var(--ak-v2-c-drawer__panel--xl--FlexBasis);
}
}
@media screen and (min-width: 1200px) {
:host([position="bottom"]) .ak-v2-c-drawer__panel {
--ak-v2-c-drawer__panel--md--FlexBasis: var(
--ak-v2-c-drawer--m-panel-bottom__panel--xl--FlexBasis
);
}
}
:where(
:host(:not([position])),
:host([position="left"]),
:host([position="right"]),
:host([position="start"]),
:host([position="end"])
)
.ak-v2-c-drawer__splitter {
--ak-v2-c-drawer__splitter--Height: var(--ak-v2-c-drawer__splitter--m-vertical--Height);
--ak-v2-c-drawer__splitter--Width: var(--ak-v2-c-drawer__splitter--m-vertical--Width);
--ak-v2-c-drawer__splitter--Cursor: var(--ak-v2-c-drawer__splitter--m-vertical--Cursor);
--ak-v2-c-drawer__splitter-handle--after--Width: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Width
);
--ak-v2-c-drawer__splitter-handle--after--Height: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Height
);
--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderTopWidth
);
--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderRightWidth
);
--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderBottomWidth
);
--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth: var(
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderLeftWidth
);
}
.ak-v2-c-drawer__splitter {
position: relative;
display: none;
width: var(--ak-v2-c-drawer__splitter--Width);
height: var(--ak-v2-c-drawer__splitter--Height);
cursor: var(--ak-v2-c-drawer__splitter--Cursor);
background-color: var(--ak-v2-c-drawer__splitter--BackgroundColor);
}
.ak-v2-c-drawer__splitter:hover {
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(
--ak-v2-c-drawer__splitter--hover__splitter-handle--after--BorderColor
);
}
.ak-v2-c-drawer__splitter:focus {
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(
--ak-v2-c-drawer__splitter--focus__splitter-handle--after--BorderColor
);
}
.ak-v2-c-drawer__splitter::after {
position: absolute;
inset-block-start: 0;
inset-block-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
content: "";
border: solid var(--ak-v2-c-drawer__splitter--after--BorderColor);
border-block-start-width: var(--ak-v2-c-drawer__splitter--after--BorderTopWidth);
border-block-end-width: var(--ak-v2-c-drawer__splitter--after--BorderBottomWidth);
border-inline-start-width: var(--ak-v2-c-drawer__splitter--after--BorderLeftWidth);
border-inline-end-width: var(--ak-v2-c-drawer__splitter--after--BorderRightWidth);
}
.ak-v2-c-drawer__splitter-handle {
position: absolute;
inset-block-start: var(--ak-v2-c-drawer__splitter-handle--Top);
inset-inline-start: var(--ak-v2-c-drawer__splitter-handle--Left);
transform: translate(-50%, -50%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"]) .ak-v2-c-drawer__splitter-handle {
transform: translate(calc(-50% * var(--ak-v2-global--inverse--multiplier)), -50%);
}
.ak-v2-c-drawer__splitter-handle::after {
display: block;
width: var(--ak-v2-c-drawer__splitter-handle--after--Width);
height: var(--ak-v2-c-drawer__splitter-handle--after--Height);
content: "";
border-color: var(--ak-v2-c-drawer__splitter-handle--after--BorderColor);
border-style: solid;
border-block-start-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth);
border-block-end-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth);
border-inline-start-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth);
border-inline-end-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth);
}
@media screen and (min-width: 768px) {
:host {
min-width: var(--ak-v2-c-drawer__panel--MinWidth);
}
:host([expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
box-shadow: var(--ak-v2-c-drawer--m-expanded__panel--BoxShadow);
}
:host([expanded][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer__panel--md--FlexBasis--min: var(
--ak-v2-c-drawer__panel--m-resizable--md--FlexBasis--min
);
flex-direction: var(--ak-v2-c-drawer__panel--m-resizable--FlexDirection);
min-width: var(--ak-v2-c-drawer__panel--m-resizable--MinWidth);
}
:host([expanded][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
width: 0;
height: 0;
}
:host([expanded][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__splitter {
flex-shrink: 0;
}
:host([expanded][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__panel-main {
flex-shrink: 1;
}
:host([position="left"]) {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(
--ak-v2-c-drawer--m-expanded--m-panel-left__panel--BoxShadow
);
}
:host([position="left"][inline])
> .ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable),
:host([position="left"][static])
> .ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable) {
padding-inline-start: 0;
padding-inline-end: var(--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight);
}
:host([position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
inset-inline-start: auto;
inset-inline-end: 0;
}
:host([position="left"][expanded][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__splitter {
--ak-v2-c-drawer__splitter-handle--Left: var(
--ak-v2-c-drawer--m-panel-left__splitter-handle--Left
);
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: var(
--ak-v2-c-drawer--m-panel-left__splitter--after--BorderLeftWidth
);
order: 1;
}
:host([position="bottom"]) {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(
--ak-v2-c-drawer--m-expanded--m-panel-bottom__panel--BoxShadow
);
--ak-v2-c-drawer__panel--MaxHeight: 100%;
--ak-v2-c-drawer__panel--FlexBasis--min: var(
--ak-v2-c-drawer--m-panel-bottom__panel--FlexBasis--min
);
min-width: auto;
min-height: var(--ak-v2-c-drawer--m-panel-bottom__panel--md--MinHeight);
}
:host([position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
inset-block-start: 0;
inset-inline-start: auto;
width: 100%;
height: var(--ak-v2-c-drawer--m-panel-bottom__panel--after--Height);
}
:host([position="bottom"][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer__panel--md--FlexBasis--min: var(
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--md--FlexBasis--min
);
--ak-v2-c-drawer__panel--m-resizable--FlexDirection: var(
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--FlexDirection
);
--ak-v2-c-drawer__panel--m-resizable--MinWidth: 0;
min-height: var(--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--MinHeight);
}
:host([position="bottom"][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__splitter {
--ak-v2-c-drawer__splitter-handle--Top: var(
--ak-v2-c-drawer--m-panel-bottom__splitter-handle--Top
);
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
--ak-v2-c-drawer__splitter--after--BorderBottomWidth: var(
--ak-v2-c-drawer--m-panel-bottom__splitter--after--BorderBottomWidth
);
}
:host([position="left"][inline]:not([no-border], [resizable]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable),
:host([position="left"][static]:not([no-border], [resizable]))
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable) {
padding-inline-start: 0;
padding-inline-end: var(--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight);
}
:host([inline][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__splitter {
--ak-v2-c-drawer__splitter--m-vertical--Width: var(
--ak-v2-c-drawer--m-inline__splitter--m-vertical--Width
);
--ak-v2-c-drawer__splitter-handle--Left: var(
--ak-v2-c-drawer--m-inline__splitter-handle--Left
);
--ak-v2-c-drawer__splitter--after--BorderRightWidth: var(
--ak-v2-c-drawer--m-inline__splitter--after--BorderRightWidth
);
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: var(
--ak-v2-c-drawer--m-inline__splitter--after--BorderLeftWidth
);
outline-offset: var(--ak-v2-c-drawer--m-inline__splitter--focus--OutlineOffset);
}
:host([position="bottom"][inline][resizable])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel
> .ak-v2-c-drawer__splitter {
--ak-v2-c-drawer__splitter--Height: var(
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--Height
);
--ak-v2-c-drawer__splitter-handle--Top: var(
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter-handle--Top
);
--ak-v2-c-drawer__splitter--after--BorderTopWidth: var(
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--after--BorderTopWidth
);
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: 0;
}
:host([no-panel-border]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
}
.ak-v2-c-drawer__splitter {
display: block;
}
}
@media (min-width: 768px) {
:host([width="25"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
}
:host([width="33"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
}
:host([width="50"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
}
:host([width="66"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
}
:host([width="75"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
}
:host([width="100"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
}
}
@media (min-width: 992px) {
:host([width="25-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
}
:host([width="33-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
}
:host([width="50-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
}
:host([width="66-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
}
:host([width="75-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
}
:host([width="100-on-lg"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
}
}
@media (min-width: 1200px) {
:host([width="25-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
}
:host([width="33-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
}
:host([width="50-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
}
:host([width="66-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
}
:host([width="75-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
}
:host([width="100-on-xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
}
}
@media (min-width: 1450px) {
:host([width="25-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
}
:host([width="33-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
}
:host([width="50-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
}
:host([width="66-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
}
:host([width="75-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
}
:host([width="100-on-2xl"]) {
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
}
}
@media (min-width: 768px) {
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
flex-shrink: 1;
}
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
}
:host([inline]:not([no-border])),
:host([static]:not([no-border])) {
background-color: var(
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
);
}
}
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
overflow-x: auto;
}
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
transform: translateX(0);
}
:host([inline][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline][position="left"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline][position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([inline][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateY(100%);
}
:host([inline][expanded][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-block-end: 0;
transform: translateY(0);
}
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([static][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([static][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
@media (min-width: 992px) {
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
flex-shrink: 1;
}
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
}
:host([inline-on-lg]:not([no-border])),
:host([static-on-lg]:not([no-border])) {
background-color: var(
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
);
}
}
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
overflow-x: auto;
}
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-lg])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-lg][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
transform: translateX(0);
}
:host([inline-on-lg][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-lg][position="left"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-lg][position="left"][expanded])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([inline-on-lg][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateY(100%);
}
:host([inline-on-lg][expanded][position="bottom"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-block-end: 0;
transform: translateY(0);
}
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([static-on-lg][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([static-on-lg][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
@media (min-width: 1200px) {
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
flex-shrink: 1;
}
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
}
:host([inline-on-xl]:not([no-border])),
:host([static-on-xl]:not([no-border])) {
background-color: var(
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
);
}
}
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
overflow-x: auto;
}
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-xl])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-xl][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
transform: translateX(0);
}
:host([inline-on-xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-xl][position="left"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-xl][position="left"][expanded])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([inline-on-xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateY(100%);
}
:host([inline-on-xl][expanded][position="bottom"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-block-end: 0;
transform: translateY(0);
}
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([static-on-xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([static-on-xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
@media (min-width: 1450px) {
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
flex-shrink: 1;
}
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
}
:host([inline-on-2xl]:not([no-border])),
:host([static-on-2xl]:not([no-border])) {
background-color: var(
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
);
}
}
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
overflow-x: auto;
}
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-2xl])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-2xl][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
transform: translateX(0);
}
:host([inline-on-2xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-start: 0;
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateX(-100%);
}
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
:host([inline-on-2xl][position="left"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
}
:host([inline-on-2xl][position="left"][expanded])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([inline-on-2xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
transform: translateY(100%);
}
:host([inline-on-2xl][expanded][position="bottom"])
.ak-v2-c-drawer__main
> .ak-v2-c-drawer__panel {
margin-block-end: 0;
transform: translateY(0);
}
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
:host([static-on-2xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
margin-inline-end: 0;
transform: translateX(0);
}
:host([static-on-2xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
transform: translateX(0);
}
@media screen and (min-width: 1200px) {
:host([position="bottom"]) {
--ak-v2-c-drawer__panel--MinWidth: auto;
--ak-v2-c-drawer__panel--MinHeight: var(
--ak-v2-c-drawer--m-panel-bottom__panel--xl--MinHeight
);
}
}
`;
//
export default styles;

View File

@@ -1,11 +1,11 @@
import { Drawer } from "./ak-drawer.component.js";
import { AkDrawer } from "./ak-drawer.component.js";
export { Drawer };
export { AkDrawer };
window.customElements.define("ak-drawer", Drawer);
window.customElements.define("ak-drawer", AkDrawer);
declare global {
interface HTMLElementTagNameMap {
"ak-drawer": Drawer;
"ak-drawer": AkDrawer;
}
}

View File

@@ -0,0 +1,195 @@
import { type AkDrawer } from "./ak-drawer.component";
import { match, P } from "ts-pattern";
import { ReactiveController, ReactiveControllerHost } from "lit";
type DrawerResizeControllerHost = ReactiveControllerHost & AkDrawer;
type Position = "start" | "end" | "left" | "right" | "bottom";
const oneOf = P.union;
const DEFAULT_SIZE_PROPERTY_NAME = "--ak-v2-c-drawer__panel--md--FlexBasis";
const DEFAULT_RESIZE_INCREMENT = 5;
interface ResizeControllerProps {
sizeProperty?: string;
resizeIncrement?: number;
}
export class DrawerResizeController implements ReactiveController {
#abortController: AbortController | null = null;
#positions: {
start: number;
end: number;
bottom: number;
} = { start: 0, end: 0, bottom: 0 };
public resizeIncrement: number;
public sizeProperty: string;
constructor(
private host: DrawerResizeControllerHost,
props: ResizeControllerProps = {},
) {
this.resizeIncrement = props.resizeIncrement ?? DEFAULT_RESIZE_INCREMENT;
this.sizeProperty = props.sizeProperty ?? DEFAULT_SIZE_PROPERTY_NAME;
}
endController() {
this.#abortController?.abort();
this.#abortController = null;
}
restartController() {
this.endController();
this.#abortController = new AbortController();
return this.#abortController.signal;
}
hostQ(part: string): HTMLElement {
const element = this.host.renderRoot.querySelector(part);
if (element === null || !(element instanceof HTMLElement)) {
throw new Error(`Could not identify requested part ${element}`);
}
return element;
}
get drawer() {
return this.hostQ('[part="drawer"]');
}
get panel() {
return this.hostQ('[part="drawer-panel"]');
}
get content() {
return this.hostQ('[part="drawer-panel-main"]');
}
get splitter() {
return this.hostQ('[part="drawer-splitter"]');
}
get inline() {
return this.host.hasAttribute("inline");
}
get position(): Position {
return (this.host.getAttribute("position") || "end") as Position;
}
initPositions() {
const pan = this.panel.getBoundingClientRect();
this.#positions = { start: pan.left, end: pan.right, bottom: pan.bottom };
}
setResizing(resizing: boolean = true) {
if (resizing) {
this.host.setAttribute("resizing", "");
} else {
this.host.removeAttribute("resizing");
}
}
get isResizing() {
return this.host.hasAttribute("resizing");
}
handleMove(ev: MouseEvent | TouchEvent, controlPosition: number) {
ev.stopPropagation();
const newSize = match(this.position)
.with(oneOf("end", "right"), () => this.#positions.end - controlPosition)
.with(oneOf("start", "left"), () => controlPosition - this.#positions.start)
.with("bottom", () => this.#positions.bottom - controlPosition)
.otherwise(() => {
throw new Error(`Do not recognize position: ${this.position}`);
});
if (this.position === "bottom") {
this.panel.style.overflowAnchor = "none";
}
this.panel.style.setProperty(DEFAULT_SIZE_PROPERTY_NAME, `${newSize}px`);
}
handleMouseMove = (ev: MouseEvent) => {
this.handleMove(ev, this.position === "bottom" ? ev.clientY : ev.clientX);
};
handleTouchMove = (ev: TouchEvent) => {
ev.preventDefault();
ev.stopImmediatePropagation();
const touch = ev.touches[0];
this.handleMove(ev, this.position === "bottom" ? touch.clientY : touch.clientX);
};
handleMouseUp = () => {
this.setResizing(false);
this.initPositions();
this.restartController();
};
handleTouchEnd = (ev: TouchEvent) => {
ev.stopPropagation();
this.handleMouseUp();
};
handleTouchStart = (ev: TouchEvent) => {
ev.stopPropagation();
const signal = this.restartController();
document.addEventListener("touchmove", this.handleTouchMove, { passive: false, signal });
document.addEventListener("touchend", this.handleTouchEnd, { signal });
this.initPositions();
this.setResizing();
};
handleMouseDown = (ev: MouseEvent) => {
ev.stopPropagation();
ev.preventDefault();
const signal = this.restartController();
document.addEventListener("mousemove", this.handleMouseMove, { signal });
document.addEventListener("mouseup", this.handleMouseUp, { signal });
this.initPositions();
this.setResizing();
};
handleKeyDown = (ev: KeyboardEvent) => {
const key = ev.key;
const positionKeys =
this.position === "bottom" ? ["ArrowUp", "ArrowDown"] : ["ArrowLeft", "ArrowRight"];
const validKeys = ["Escape", "Enter", ...positionKeys];
// Prevent default behavior when resizing, but otherwise let it pass.
if (!validKeys.includes(key)) {
if (this.isResizing) {
ev.preventDefault();
}
return;
}
ev.preventDefault();
const delta = match([key, this.position])
.with(["ArrowRight", oneOf("end", "right")], () => -1 * this.resizeIncrement)
.with(["ArrowLeft", oneOf("end", "right")], () => this.resizeIncrement)
.with(["ArrowRight", oneOf("start", "left")], () => this.resizeIncrement)
.with(["ArrowLeft", oneOf("start", "left")], () => -1 * this.resizeIncrement)
.with(["ArrowUp", "bottom"], () => this.resizeIncrement)
.with(["ArrowDown", "bottom"], () => -1 * this.resizeIncrement)
.otherwise(() => 0);
const { height, width } = this.panel.getBoundingClientRect();
const newSize = (this.position === "bottom" ? height : width) + delta;
this.panel.style.setProperty(DEFAULT_SIZE_PROPERTY_NAME, `${newSize}px`);
};
hostConnected() {
this.host.updateComplete.then(() => {
this.initPositions();
});
}
hostDisconnected() {
this.#abortController?.abort();
this.#abortController = null;
}
}

View File

@@ -13,9 +13,10 @@ import {
NotificationsMixin,
} from "#elements/mixins/notifications";
import { SessionMixin } from "#elements/mixins/session";
import { createPaginatedNotificationListFrom } from "#elements/notifications/utils";
import type { ReactiveElementHost } from "#elements/types";
import { createPaginatedNotificationListFrom } from "#components/notifications/utils";
import { EventsApi } from "@goauthentik/api";
import { ContextProvider } from "@lit/context";

View File

@@ -20,9 +20,10 @@ import {
SessionMixin,
UIConfigContext,
} from "#elements/mixins/session";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import type { ReactiveElementHost } from "#elements/types";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import { CoreApi, SessionUser } from "@goauthentik/api";
import { setUser } from "@sentry/browser";

View File

@@ -4,10 +4,11 @@ import { MessageLevel } from "#common/messages";
import { ContextControllerRegistry } from "#elements/controllers/ContextControllerRegistry";
import { showMessage } from "#elements/messages/MessageContainer";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { createPaginatedNotificationListFrom } from "#elements/notifications/utils";
import { createMixin } from "#elements/types";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import { createPaginatedNotificationListFrom } from "#components/notifications/utils";
import { ConsoleLogger } from "#logger/browser";
import {

View File

@@ -73,9 +73,9 @@ export class FlowInspectorButton extends WithCapabilitiesConfig(AKElement) {
const drawer = document.getElementById("flow-drawer");
if (changed.has("open") && drawer) {
if (this.open) {
drawer.setAttribute("open", "");
drawer.setAttribute("expanded", "");
} else {
drawer.removeAttribute("open");
drawer.removeAttribute("expanded");
}
}
}

View File

@@ -44,8 +44,90 @@
--ak-sidebar--minimum-auto-width: 80rem;
}
html[data-theme="dark"] {
--ak-global--BackgroundColorContrast--100: var(--pf-global--palette--black-150);
/* #region Root globals, V2 */
:root {
/* ---- Background Colors ---- */
--ak-v2-global--BackgroundColor--100: #fff;
--ak-v2-global--BorderWidth--sm: 1px;
/* ---- Text Colors ---------- */
--pf-v5-global--Color--100: #151515;
/* ---- Border Colors -------- */
--ak-v2-global--BorderColor--100: #d2d2d2;
--ak-v2-global--BorderColor--200: #8a8d90;
/* ---- Box Shadows ------ */
--ak-v2-global--BoxShadow--lg:
0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08);
--ak-v2-global--BoxShadow--lg-top: 0 -0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.18);
--ak-v2-global--BoxShadow--lg-right: 0.75rem 0 0.75rem -0.5rem rgba(3, 3, 3, 0.18);
--ak-v2-global--BoxShadow--lg-bottom: 0 0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.18);
--ak-v2-global--BoxShadow--lg-left: -0.75rem 0 0.75rem -0.5rem rgba(3, 3, 3, 0.18);
/* ---- Spacers -------------- */
--ak-v2-global--spacer--xs: 0.25rem;
--ak-v2-global--spacer--sm: 0.5rem;
--ak-v2-global--spacer--md: 1rem;
--ak-v2-global--spacer--lg: 1.5rem;
--ak-v2-global--spacer--xl: 2rem;
--ak-v2-global--spacer--2xl: 3rem;
--ak-v2-global--spacer--3xl: 4rem;
--ak-v2-global--spacer--4xl: 5rem;
--ak-v2-global--spacer--form-element: 0.375rem;
--ak-v2-global--gutter: 1rem;
--ak-v2-global--gutter--md: 1.5rem;
/* ---- Z-Index -------------- */
--ak-v2-global--ZIndex--xs: 100;
--ak-v2-global--ZIndex--sm: 200;
/* ---- Animation ------------ */
--ak-v2-global--TransitionDuration: 250ms;
/* ---- Customization Bridge - */
--ak-v2-global--dark-background: var(--ak-dark-background);
}
/* -------- Dark Theme ------------------------------- */
[data-theme="dark"] {
/* ---- Background Colors ---- */
--ak-v2-global--BackgroundColor--100: #18191a;
/* ---- Text Colors ---------- */
--ak-v2-global--Color--100: #e0e0e0;
/* ---- Border Colors -------- */
--ak-v2-global--BorderColor--100: #444548;
--ak-v2-global--BorderColor--200: #444548;
/* ---- Box Shadows ------ */
--ak-v2-global--BoxShadow--lg:
0 0.5rem 1rem 0 rgba(3, 3, 3, 0.64), 0 0 0.375rem 0 rgba(3, 3, 3, 0.32);
--ak-v2-global--BoxShadow--lg-top: 0 -0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.72);
--ak-v2-global--BoxShadow--lg-right: 0.75rem 0 0.75rem -0.5rem rgba(3, 3, 3, 0.72);
--ak-v2-global--BoxShadow--lg-bottom: 0 0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.72);
--ak-v2-global--BoxShadow--lg-left: -0.75rem 0 0.75rem -0.5rem rgba(3, 3, 3, 0.72);
}
/* -------- Semantic Names -------------------------- */
:root {
/* ---- Background Colors ---- */
--ak-v2-global--ContentSurface: var(--ak-v2-global--BackgroundColor--100);
--ak-v2-global--SecondaryContentSurface: var(--ak-v2-global--BackgroundColor--200);
/* Not sure what to call this next one; this is the background color Patternfly uses when you hover
over something and it changes color to indicate it's interactive in some way. It's the same
color as the one above in their default theme. */
--ak-v2-global--AffordanceIndicatedSurface: var(--ak-v2-global--BackgroundColor--200);
/* ---- Text Colors ---- */
--ak-v2-global--PrimaryText: var(--ak-v2-global--Color--100);
/* ---- Border Colors ---- */
--ak-v2-global--StandardBorder: var(--pf-v5-global--BorderColor--100);
--ak-v2-global--InputAccentBorder: var(--pf-v5-global--BorderColor--200);
}
/* #endregion */

View File

@@ -14,6 +14,7 @@
@import "./base/globals.css";
@import "./base/common.css";
@import "./base/placeholder.css";
@import "#elements/ak-drawer/ak-drawer.root.css";
@import "#styles/locales/ja/globals.css";
@import "#styles/locales/ko/globals.css";

View File

@@ -12,6 +12,7 @@
@import "./components/Fieldset/fieldset.css";
@import "./components/Login/login.css";
@import "./components/Icon/icon.css";
@import "#elements/ak-drawer/ak-drawer.root.css";
@import "#elements/locale/ak-locale-select.css";
@import "#elements/locale/ak-locale-select.css";
@import "#flow/FlowExecutor.css";

View File

@@ -1,7 +1,7 @@
import "#components/ak-nav-buttons";
import "#elements/banner/EnterpriseStatusBanner";
import "#elements/notifications/APIDrawer";
import "#elements/notifications/NotificationDrawer";
import "#components/notifications/APIDrawer";
import "#components/notifications/NotificationDrawer";
import "#elements/router/RouterOutlet";
import { globalAK } from "#common/global";
@@ -13,15 +13,16 @@ import { AuthenticatedInterface } from "#elements/AuthenticatedInterface";
import { listen } from "#elements/decorators/listen";
import { WithBrandConfig } from "#elements/mixins/branding";
import { canAccessAdmin, WithSession } from "#elements/mixins/session";
import { AKDrawerChangeEvent } from "#elements/notifications/events";
import { ifPresent } from "#elements/utils/attributes";
import { ThemedImage } from "#elements/utils/images";
import { AKDrawerChangeEvent } from "#components/notifications/events";
import {
DrawerState,
persistDrawerParams,
readDrawerParams,
renderNotificationDrawerPanel,
} from "#elements/notifications/utils";
import { ifPresent } from "#elements/utils/attributes";
import { ThemedImage } from "#elements/utils/images";
} from "#components/notifications/utils";
import Styles from "#user/ak-interface-user.css";
import { ROUTES } from "#user/Routes";

21
web/stylelint.config.mjs Normal file
View File

@@ -0,0 +1,21 @@
/** @type { import("stylelint").Config } */
export default {
extends: "stylelint-config-standard",
rules: {
"custom-property-pattern": [
"^([A-Za-z][A-Za-z0-9]*)((__|--?)[A-Za-z0-9]+)*$",
{
message: "Expected custom property name to be kebab-case",
},
],
"selector-class-pattern": [
"^([a-z][a-z0-9]*)((__?|-)[A-Za-z0-9]+)*$",
{
message: (/** @type {string} */ selector) =>
`Expected class selector "${selector}" to be kebab-case`,
},
],
"declaration-empty-line-before": null,
"media-feature-range-notation": null,
},
};

View File

@@ -3,6 +3,10 @@
"extends": "@goauthentik/tsconfig",
"compilerOptions": {
"ignoreDeprecations": "6.0",
// Nothing references the web package, so it does not need to act as a
// composite project. Disabling composite lets us exclude `packages/`
// (each subpackage owns its own tsconfig + build) without TS6307.
"composite": false,
"types": ["node"],
"checkJs": true,
"allowJs": true,
@@ -68,6 +72,10 @@
"storybook-static",
"src/**/*.test.ts",
"./tests",
// Workspace subpackages each own their tsconfig and build. Including
// them here pulls ~1k files (notably the OpenAPI client in
// packages/client-ts) into every `tsc -p .` for no benefit.
"packages",
// TODO: @lit/localize-tools v0.8.0 has a nullish coalescing typing error.
// Remove when we upgrade past that.
"scripts/pseudolocalize.mjs",

View File

@@ -10,7 +10,15 @@ To add an application to authentik and have it display on users' **My applicatio
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard. Alternatively, you can first create a provider separately, then create the application and connect it to the provider.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
:::info
By default, if you click **New Application**, you are prompted to create the new application and a new provider. However, you can use the drop-down menu beside **New Application** to explicitly select either:
- **with New Provider...** to create the application and configure a new provider at the same time.
- **with Existing Provider...** if you have already created a provider separately, and now you want to create the application and connect it to the provider.
:::
3. In the **New application** box, define the application details, the provider type and configuration settings, and bindings for the application.
- **Application**: provide a name, an optional group for the type of application, the policy engine mode, and optional UI settings.

View File

@@ -167,24 +167,24 @@ Now that you can access the authentik Admin interface, and you have added an app
### 1. Log in to authentik as an administrator and open the authentik Admin interface.
**A.** Navigate to **Directory > Users**, and click **New User**.
**A.** Navigate to **Directory > Users**, click **New User**, and then select **Internal User**..
**B.** Fill in the **_required_** fields:
**B.** Fill in all **_required_** fields:
- **Username**: This value must be unique across all users.
- <strong className="tip">TIP</strong>: With OAuth2, front-channel logout is considered the default because most applications
(including Grafana) do not support back-channel logout.
- **Path**: The path where the user will be created. By default the new user is created in the `users` directory, but you can change that later by editing the user.
- <strong className="tip">TIP</strong>: Paths are basically directories that are used to organize your users (for example HR vs Sales, etc.). Paths do not impact access; they are purely organizational. Note that the top-level **users** directory displays all users in that directory and all sub-directories.
- **Display Name** (_optional_): The display name of the user.
- **Email** (_optional_): The email address of the user. Email addresses are used in [email stages](../../add-secure-apps/flows-stages/stages/email/index.md), as an alternative method to log in, by services to request an email address, and if configured, to receive [notifications](../../sys-mgmt/events/notifications.md).
- **Active** (_optional_): Define if the newly created user account is active. Selected by default.
- **Path**: The path where the user will be created. By default the new user is created in the `users` folder, but you can change that later by editing the user.
- <strong className="tip">TIP</strong>: Paths are directories that are used to organize your
users (for example HR vs Sales, etc.). Paths do not impact access; they are purely
organizational. Note that the top-level **users** folder displays all users in that folder
and all sub-folders.
- **Attributes** (_optional_): [Custom attributes](../../users-sources/user/user_ref.mdx) for the user, in YAML or JSON format. These attributes can be used to enforce additional prompts on authentication stages or define conditions to enforce specific policies if the current implementation does not fit your use case. The value is an empty dictionary by default.
For information about the **_optional_** fields below, refer to our [documentation on managing users](../../users-sources/user/user_basic_operations.md#create-a-user).
C. Click **Create**.
- **Name**: The display name of the user.
- **Email**: The email address of the user. This is required for many integrations.
- **Is active**: Define the newly created user account as active.
- **Attributes**: You can leave this empty for this tutorial. This field can be used to store custom attributes for the user, in YAML or JSON format. These attributes can then be used within property mappings and policies.
**C.** Click **Create User**.
For more information refer to our [documentation on managing users](../../users-sources/user/user_basic_operations.md#create-a-user).
### 2. Verify that the new user was created

View File

@@ -428,6 +428,53 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.12
- sources/oauth: Fix InvalidAudienceError in id_token fallback (cherry-pick #20096 to version-2025.12) (#20122)
- web/admin: fix default binding order (cherry-pick #19943 to version-2025.12) (#19945)
## Fixed in 2025.12.5
- core: bump cbor2 from 5.8.0 to 5.9.0 (cherry-pick #21094 to version-2025.12) (#21095)
- core: bump django from 5.2.11 to 5.2.12 (cherry-pick #20719 to version-2025.12) (#20737)
- core: bump django from v5.2.12 to 5.2.13 (cherry-pick #21520 to version-2025.12) (#21525)
- docs: Add note on skipping object syncing (cherry-pick #20882 to version-2025.12) (#20893)
- endpoints: fix infinite recursion in stage with unsupported connector (cherry-pick #20485 to version-2025.12) (#20513)
- enterprise: add `ES384` to enterprise license algorithms (cherry-pick #20507 to version-2025.12) (#20509)
- events: avoid implicitly setting context from login_failed event (cherry-pick #21045 to version-2025.12) (#21049)
- flows: continuous login debug 2025.12 (#21044)
- security: [CVE-2026-40165](../../security/cves/CVE-2026-40165) (#22275)
- security: [CVE-2026-40166](../../security/cves/CVE-2026-40166) (#22276)
- security: [CVE-2026-40172](https://github.com/goauthentik/authentik/security/advisories/GHSA-h6x7-hjjc-wjc9) (#22277)
- security: [CVE-2026-41577](https://github.com/goauthentik/authentik/security/advisories/GHSA-4v4x-x5pr-8gp2) (#22278)
- security: [CVE-2026-42849](../../security/cves/CVE-2026-42849) (#22279)
- internal: Automated internal backport: GHSA-5wcc-hf24-rf5h.sec.patch to authentik-2025.12 (#22280)
- internal: Automated internal backport: GHSA-973w-j457-rp2m.sec.patch to authentik-2025.12 (#22281)
- internal: fix lint (cherry-pick #22263 to version-2025.12) (#22306)
- internal: make http timeouts configurable (cherry-pick #20472 to version-2025.12) (#20566)
- policies: fix PolicyEngineMode ALL with static binding optimization (cherry-pick #20430 to version-2025.12) (#20523)
- policies: measure policy process from manager (cherry-pick #20477 to version-2025.12) (#20480)
- providers/oauth2: allow cross provider token introspection for federated providers (cherry-pick #21513 to version-2025.12) (#21747)
- providers/oauth2: clip device authorization scope against the provider's ScopeMapping set (cherry-pick #21701 to version-2025.12) (#21798)
- providers/oauth2: deactivate locale after testing (cherry-pick #20518 to version-2025.12) (#20525)
- providers/oauth2: device code flow client id via auth header (cherry-pick #20457 to version-2025.12) (#21803)
- providers/oauth2: don't auto-set redirect_uri (cherry-pick #21746 to version-2025.12) (#21749)
- providers/proxy: move search path to query instead of runtime parameter (cherry-pick #20662 to version-2025.12) (#20692)
- providers/radius: fix message authenticator validation (cherry-pick #21824 to version-2025.12) (#21827)
- providers/saml: Fix redirect for saml slo (cherry-pick #21258 to version-2025.12) (#21283)
- proviers/ldap: avoid concurrent header writes in API Client (cherry-pick #21223 to version-2025.12) (#21227)
- root: do not rely on npm cli for version bump (cherry-pick #20276 to version-2025.12) (#20320)
- root: fix compose generation for patch releases release candidates (cherry-pick #21353 to version-2025.12) (#21354)
- root: update django to 5.2.14 (cherry-pick #22064 to version-2025.12) (#22065)
- sources/ldap: fix exception in ldap debug endpoint (cherry-pick #21219 to version-2025.12) (#21220)
- sources/saml: update handling statusmessage (cherry-pick #19739 to version-2025.12) (#20066)
- stages/user_login: log correct user when session binding is broken (cherry-pick #20094 to version-2025.12) (#20452)
- web: Fix duplicate Turnstile widgets after extended idle (cherry-pick #21380 to version-2025.12) (#21472)
- web: Fix locale selector in compatibility mode. (cherry-pick #19946 to version-2025.12) (#20088)
- web: re-update package-lock.json to include missing tree-sitter references
- web/admin: fix missing OSM referrerPolicy header (cherry-pick #20984 to version-2025.12) (#20989)
- web/admin: Fix SCIM page_size UI issue (cherry-pick #20890 to version-2025.12) (#20928)
- web/admin: handle non-string values in formatUUID to prevent Event Log crash (cherry-pick #20804 to version-2025.12) (#21051)
- web/flows: add continuous flow 2025.12 (#20362)
- web/flows: prevent leader tab deadlock in continuous login flow (cherry-pick #21583 to version-2025.12) (#21626)
- web/packages: Rework SFE rendering (cherry-pick #21833 to version-2025.12) (#21851)
- web/sfe: bug: polyfill needed to supply Object.assign() to IE11. (cherry-pick #20126 to version-2025.12) (#20136)
## API Changes
### authentik (v 2025.12.0-rc1)

View File

@@ -359,6 +359,39 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2026.2
- web/flows: continuous login (cherry-pick #19862 to version-2026.2) (#20712)
- web/rbac: disambiguate duplicate permission names in initial permissions (cherry-pick #20786 to version-2026.2) (#20805)
## Fixed in 2026.2.3
- blueprints: fix reconcile calling @property (cherry-pick #21576 to version-2026.2) (#21616)
- core: bump django from v5.2.12 to 5.2.13 (cherry-pick #21520 to version-2026.2) (#21526)
- core: fix policy binding objects not being nullable (cherry-pick #21421 to version-2026.2) (#21481)
- core: fix search for app entitlements failing (cherry-pick #21944 to version-2026.2) (#21988)
- endpoints: fix tasks failing (cherry-pick #20904 to version-2026.2) (#21538)
- events: fix `destination_group_obj` not being nullable (cherry-pick #22161 to version-2026.2) (#22165)
- security: [CVE-2026-40165](../../security/cves/CVE-2026-40165) (#22282)
- security: [CVE-2026-40166](../../security/cves/CVE-2026-40166) (#22283)
- security: [CVE-2026-40172](https://github.com/goauthentik/authentik/security/advisories/GHSA-h6x7-hjjc-wjc9) (#22284)
- security: [CVE-2026-41569](../../security/cves/CVE-2026-41569) (#22285)
- security: [CVE-2026-41577](https://github.com/goauthentik/authentik/security/advisories/GHSA-4v4x-x5pr-8gp2) (#22286)
- security: [CVE-2026-42849](../../security/cves/CVE-2026-42849) (#22287)
- internal: Automated internal backport: GHSA-5wcc-hf24-rf5h.sec.patch to authentik-2026.2 (#22288)
- internal: Automated internal backport: GHSA-973w-j457-rp2m.sec.patch to authentik-2026.2 (#22289)
- internal: fix lint (#22263)
- lib/sync/outgoing: avoid expensive query to get number of sync pages (cherry-pick #21575 to version-2026.2) (#21581)
- packages/django-dramatiq-postgres: reset db connections in raise_connection_error (cherry-pick #21577 to version-2026.2) (#21599)
- packages/django-dramatiq-postgres/broker: avoid task processing stopping on decode error (cherry-pick #22110 to version-2026.2) (#22127)
- providers/oauth2: allow cross provider token introspection for federated providers (cherry-pick #21513 to version-2026.2) (#21748)
- providers/oauth2: clip device authorization scope against the provider's ScopeMapping set (cherry-pick #21701 to version-2026.2) (#21799)
- providers/oauth2: don't auto-set redirect_uri (cherry-pick #21746 to version-2026.2) (#21750)
- providers/oauth2: fix time logic in refresh_token_threshold (cherry-pick #21537 to version-2026.2) (#21598)
- providers/radius: fix message authenticator validation (cherry-pick #21824 to version-2026.2) (#21828)
- rbac: ensure migration 0056 runs before 0010 removes group field (cherry-pick #21964 to version-2026.2) (#22033)
- release: 2026.2.3-rc1
- root: update django to 5.2.14 (cherry-pick #22064 to version-2026.2) (#22066)
- tenants/settings: present unset flags as `False` (cherry-pick #22162 to version-2026.2) (#22164)
- web: Fix duplicate Turnstile widgets after extended idle (cherry-pick #21380 to version-2026.2) (#21473)
- web/flows: prevent leader tab deadlock in continuous login flow (cherry-pick #21583 to version-2026.2) (#21627)
- web/packages: Rework SFE rendering (cherry-pick #21833 to version-2026.2) (#21850)
## API Changes
### authentik (v2026.2.0)

View File

@@ -10,7 +10,7 @@ beta: true
- **Fleet Conditional Access**: :ak-enterprise authentik can now verify user devices using Fleet certificates via the Fleet Connector and an mTLS stage, without the authentik agent.
- **`AKQL` is now open source**: The `AKQL` search query language for logs and users, previously enterprise-only, is now free for everyone to use.
- **Command Palette and wizard upgrades**: A new `Cmd + K` command palette to search the authentik UI, alongside reworked wizards including a new user creation wizard, improved binding wizard, and new invitation wizard.
- **Performance improvements**: The new Rust worker entrypoint drops memory usage by approximately 200 MB per worker container, opens one fewer PostgreSQL connection per worker, and makes the Admin interface less resource-intensive through lazy-loaded modals.
- **Performance improvements**: The new Rust worker entrypoint drops memory usage by approximately 200 MB per worker container, and opens one fewer PostgreSQL connection per worker. The Admin interface is less resource-intensive through lazy-loaded modals.
## Breaking changes

View File

@@ -12,7 +12,7 @@ draft: true
## Upgrading
This release does not introduce any new requirements. You can follow the upgrade instructions below; for more detailed information about upgrading authentik, refer to our [Upgrade documentation](../install-config/upgrade.mdx).
This release does not introduce any new requirements. You can follow the upgrade instructions below; for more detailed information about upgrading authentik, refer to our [Upgrade documentation](../../install-config/upgrade.mdx).
:::warning
When you upgrade, be aware that the version of the authentik instance and of any outposts must be the same. We recommend that you always upgrade any outposts at the same time you upgrade your authentik instance.

View File

@@ -0,0 +1,33 @@
# CVE-2026-40165
_Reported by [@kodareef5](https://github.com/kodareef5), [@Android-Login-Analysis](https://github.com/Android-Login-Analysis), and [@AyushParkara](https://github.com/AyushParkara)_
## SAML Source Fails to Validate Assertion Conditions
### Summary
Due to how authentik used to extract the NameID value from a SAML assertion, it was possible for an attacker to trick authentik into only seeing a part of the NameID value, potentially allowing an attacker to get access to other accounts.
### Patches
authentik 2025.12.5 and 2026.2.3 fix this issue, for other versions the workaround below can be used.
### Impact
This issue can be exploited given an authentik instance with a SAML Source, where the attacker has an account on the SAML Source and the ability to modify their NameID value (commonly username or E-mail), and XML Signing is enabled. The attacker can modify the SAML assertion given to authentik by injecting a comment within the NameID value, which effectively truncates the NameID value to the snippet before the comment, and give the attack access to any user account.
### Workarounds
Create a SAML Source property mapping with the following expression and add it to all SAML Sources:
```python
if name_id.text != "".join(name_id.itertext()):
raise ValueError("Mismatched NameID")
return {}
```
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).

View File

@@ -0,0 +1,27 @@
# CVE-2026-40166
_Reported by [@Colbascov](https://github.com/Colbascov)_
## Non-admin users can read confidential OAuth provider client secrets via the access token endpoint
### Summary
Authenticated non-admin users with at least one OAuth2 access token can retrieve the `client_secret` of confidential OAuth2 providers they have previously authenticated against, via `GET /api/v3/oauth2/access_tokens/`. The API response includes a nested `provider` object containing `client_id` and `client_secret` for providers configured with `client_type: confidential`, which should not be accessible to low-privilege users.
### Patches
authentik 2025.12.5 and 2026.2.3 fix this issue; for other versions the workaround can be used.
### Impact
Any authenticated non-admin user who has previously completed an OAuth2 flow against a confidential provider — and therefore has an access token object returned by `/api/v3/oauth2/access_tokens/` — can read that provider's `client_secret`. Exposure is limited to providers the user has access to and has logged into at least once; users cannot read secrets for providers they have never authenticated against. This could allow unauthorized reuse of confidential client credentials depending on the provider configuration.
### Workarounds
Restrict API access to `/api/v3/oauth2/access_tokens/` for non-admin users, or review and limit which users are permitted to complete OAuth2 flows against confidential providers until a patched version can be applied.
### For more information
If you have any questions or comments about this advisory:
- Email us at [[security@goauthentik.io](mailto:security@goauthentik.io)](mailto:security@goauthentik.io)

View File

@@ -0,0 +1,33 @@
# CVE-2026-41569
_Reported by [@jmecom](https://github.com/jmecom) and [@AyushParkara](https://github.com/AyushParkara)_
## WS-Federation wreply Origin Bypass (CVE-2026-41569)
### Summary
The WS-Federation provider validates the user-supplied `wreply` parameter using a raw string prefix check rather than proper URL parsing. An attacker who can craft a login link can supply a `wreply` value on a different origin that passes the check (e.g. `https://portal.example.com.evil.tld/`), causing the victim's browser to POST the signed WS-Federation login response to attacker-controlled infrastructure.
### Patches
authentik 2025.12.5 and 2026.2.3 fix this issue.
### Impact
The WS-Federation sign-in processor accepted any `wreply` whose string value started with the configured Reply URL, not correctly comparing the domain.
Once accepted, the attacker-controlled `wreply` is used as the autosubmit destination, and the victim's browser immediately POSTs the signed WS-Federation response (`wresult`) to that URL. The response is a valid signed authentication artifact; in many relying-party configurations it is replayable to the legitimate ACS endpoint, enabling victim impersonation in the target application.
The fix replaces the string prefix check with proper URL parsing, comparing scheme, host, and path independently:
Only WS-Federation providers (an enterprise feature) with a prefix-ambiguous Reply URL are affected. If the Reply URL is already path-specific (e.g. `https://portal.example.com/wsfed/acs`), the host-extension bypass does not apply.
### Workarounds
Configure the WS-Federation provider's Reply URL with a specific path (e.g. `https://portal.example.com/wsfed/acs`) rather than a bare hostname. This prevents the host-extension bypass without patching, though upgrading is strongly preferred.
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@@ -0,0 +1,27 @@
# CVE-2026-42849
_Reported by Jan Kahmen, [turingpoint GmbH](https://turingpoint.de/en/)_
## Reflected XSS in SFE
### Summary
Due to the implementation of stages in the SFE (Simple Flow Executor) in order to make the interface more compatible with legacy browsers, it was possible to use an XSS exploit in the AutosubmitStage.
### Patches
authentik 2025.12.5 and 2026.2.3 fix this issue.
### Impact
The SFE (Simple Flow Executor) was susceptible to an XSS exploit. This could allow an attacker to redirect web requests containing tokens, hijack the session or take other malicious actions.
This is possible when an OAuth2 provider is configured, either through the redirect_uri when a very broad regex is used, or through the state value.
The SFE previously used jQuery without explicit sanitization, which, compared to the rest of our interfaces, did not sufficiently protect from malicious input values.
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io).

View File

@@ -0,0 +1,33 @@
# GHSA-5wcc-hf24-rf5h
_Reported by [@bugbunny-research](https://github.com/bugbunny-research)_
## Unauthenticated Access via Client-Controlled X-Original-URI Header in Nginx Forward-Auth Mode
### Summary
In nginx forward-auth mode, the authentik outpost reads the forwarded request URL from a header that nginx does not set, but that a client can freely inject. By crafting this header to point at an internal outpost path, an unauthenticated attacker can cause the outpost to return HTTP 200 — causing nginx to forward the original request to the protected backend without authentication.
### Patches
authentik 2025.12.5 and 2026.2.3 fix this issue.
### Impact
This vulnerability only affects deployments using authentik's nginx forward-auth integration. Traefik, Caddy, and proxy mode deployments are not affected.
In nginx forward-auth mode, the outpost builds the URL it evaluates from a header that nginx never sets but clients can freely inject. Because nginx's `auth_request` module forwards all client headers to the authentication subrequest, an attacker-supplied value reaches the outpost unmodified.
The outpost unconditionally allows requests whose forwarded URL path begins with `/outpost.goauthentik.io/` (to permit internal endpoints such as the OAuth callback). An attacker can set the injected header to any path under that prefix, causing the outpost to return 200 and nginx to proxy the original request to the backend as if authenticated.
The attack requires only a single HTTP header — no account, session, or prior knowledge of the target application. Any resource behind the nginx gateway is accessible: reads, writes, and deletes depending on what the backend exposes. The CVSS 3.1 score is 10.0 (Critical).
### Workarounds
Operators can mitigate this immediately at the nginx layer by explicitly clearing the `X-Original-URI` header in the nginx location block that proxies traffic to the outpost. This prevents the attacker-supplied value from reaching the outpost regardless of the application-level logic. Refer to the nginx documentation for `proxy_set_header` to clear individual headers.
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@@ -29,15 +29,15 @@ Service accounts have certain limitations compared to regular user accounts:
To create a service account:
1. In the authentik **Admin interface**, navigate to **Directory** > **Users**.
2. Click the **Create Service Account** button.
2. Click **New User**, and then select **Service Account**.
3. Configure the following settings:
- **Username**: The user's primary identifier (150 characters or fewer).
- **Create Group**: Enabling this toggle will create a group named after the user, with the user as a member.
- **Username**: The account's primary identifier (150 characters or fewer).
- **Create Group** (_optional_): Enabling this toggle will create a group named after the account, with the user as a member.
- **Expiring**: If selected, the token will expire and be automatically rotated upon expiration.
- **Expires on**: Sets the expiration date (defaults to 1 year from the creation date).
4. Click **Create Service Account**.
After creating the service account, you'll see a confirmation screen that shows the username and generated password (token). Make sure to copy this information somewhere secure because you'll need it for authentication.
4. Click **Next**.
View the confirmation screen that shows the username and generated password (token). Make sure to copy this information somewhere secure as you'll need it for authentication. If you need the token later, navigate to the **Directory -> Tokens and App passwords** and copy the one for your service account.
5. Click **Close**.
## Token properties

View File

@@ -10,24 +10,20 @@ The following topics are for the basic management of users: how to create, modif
> If you want to automate user creation, you can do that either by [invitations](./invitations.md), [`user_write` stage](../../add-secure-apps/flows-stages/stages/user_write/index.md), or [using the API](/api/reference/core-users-create).
1. In the Admin interface of your authentik instance, select **Directory** > **Users** in the left side menu.
1. In the Admin interface of your authentik instance, select **Directory** > **Users** in the left menu.
2. In the **User folders** area, select the folder where you want to create a user.
3. Click **New User** (for a default user).
3. Click **New User**, and then select either **Internal User** or **External User**.
4. Fill in the required fields:
- **Username**: This value must be unique across your user folders.
- **Username**: This value must be unique across all users.
- **Display Name** (_optional_): The display name of the user.
- **Email** (_optional_): The email address of the user. For more information about email addresses refer to our [Email documentation](../../install-config/email.mdx).
- **Active** (_optional_): Define if the newly created user account is active. Selected by default.
- **Path**: The path where the user will be created. It will be automatically populated with the folder you selected in the previous step.
5. Fill the **_optional_** fields if needed:
- **Name**: The display name of the user.
- **Email**: The email address of the user. Email addresses are used in [email stages](../../add-secure-apps/flows-stages/stages/email/index.md) and, if configured, to receive [notifications](../../sys-mgmt/events/notifications.md).
- **Is active**: Define if the newly created user account is active. Selected by default.
- **Attributes**: Custom attributes definition for the user, in YAML or JSON format. These attributes can be used to enforce additional prompts on authentication stages or define conditions to enforce specific policies if the current implementation does not fit your use case. The value is an empty dictionary by default.
6. Click **Create User**
You should see a confirmation pop-up on the top-right of the screen that the user has been created, and see the new user in the user list. You can directly click the username if you want to [modify your user](./user_basic_operations.md#modify-a-user).
6. Click **Create**.
You should see a confirmation pop-up on the top-right of the screen that the user has been created, and see the new user in the user list. You can directly click the username if you want to [modify your user](./user_basic_operations.md#modify-a-user).
:::info
To create a super-user, you need to add the user to a group that has super-user permissions. For more information, refer to [Create a Group](../groups/manage_groups.mdx#create-a-group).

View File

@@ -6,7 +6,7 @@ support_level: community
## What is AFFiNE?
> AFFiNE is an open-source platform that allows you to bring together documents, whiteboards, and databases. It is a reliable tool designed to create a professional workspace for your work. With AFFiNE, you can focus on practicality and efficiency, making it easier to collaborate on your projects.
> AFFiNE is an open-source, self-hostable workspace for documents, whiteboards, and databases.
>
> -- https://affine.pro/
@@ -29,28 +29,26 @@ To support the integration of AFFiNE with authentik, you need to create an appli
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
- Create a `Strict` redirect URI and set to `https://affine.company/oauth/callback`.
- Select any available signing key.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
- Add one `Strict` redirect URI and set it to `https://affine.company/oauth/callback`.
- Select any available signing key.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## AFFiNE configuration
1. Log in to AFFiNE as an administrator.
2. Navigate to the Admin Panel of your instance by clicking on your profile picture.
3. Navigate to **Settings** > **OAuth**.
4. Under **OIDC OAuth provider config**, set the following JSON data:
2. Click your profile picture and navigate to **Admin Panel** > **Settings** > **OAuth**.
3. Under **OIDC OAuth provider config**, set the following JSON data:
```json
{
"args": {},
"issuer": "https://authentik.company/application/o/<application_slug>/",
"issuer": "https://authentik.company/application/o/<application_slug>",
"clientId": "<Client ID from authentik>",
"clientSecret": "<Client Secret from authentik>"
}
@@ -60,8 +58,9 @@ To support the integration of AFFiNE with authentik, you need to create an appli
## Configuration verification
To verify the integration of authentik with AFFiNE, log out of AFFiNE, then on the login page click on **Continue with OIDC**. You should be redirected to authentik, and once authenticated, logged in to AFFiNE.
To confirm that authentik is properly configured with AFFiNE, open AFFiNE and log in using the **Continue with OIDC** login option. You should be redirected to authentik for authentication and then redirected back to AFFiNE.
## Resources
- [AFFiNE Docs - OAuth 2.0 ](https://docs.affine.pro/self-host-affine/administer/oauth-2-0#oidc)
- [AFFiNE OAuth 2.0 documentation](https://docs.affine.pro/self-host-affine/administer/oauth-2-0#oidc)
- [AFFiNE OIDC provider source](https://github.com/toeverything/AFFiNE/blob/canary/packages/backend/server/src/plugins/oauth/providers/oidc.ts)

View File

@@ -42,7 +42,7 @@ To support the integration of ChatGPT with authentik, you need to create an appl
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings. Take note of the **Slug** value because it will be required later.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
@@ -90,7 +90,7 @@ To support the integration of ChatGPT with authentik, you need to create an appl
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings. Take note of the **slug** as it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.

View File

@@ -10,8 +10,8 @@ support_level: community
>
> -- https://www.espocrm.com/
:::warning
This guide does _not_ cover Team Mapping. Please refer to EspoCRM's [documentation](https://docs.espocrm.com/administration/oidc/#team-mapping).
:::info Scope
This guide covers OIDC login for the primary EspoCRM interface. For team mapping or portal-specific OIDC configuration, refer to EspoCRM's [OIDC documentation](https://docs.espocrm.com/administration/oidc/).
:::
## Preparation
@@ -33,37 +33,41 @@ To support the integration of EspoCRM with authentik, you need to create an appl
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
- Set a `Strict` redirect URI to `https://espocrm.company/oauth-callback.php`.
- Select any available signing key.
- Under **Advanced protocol settings**, set **Subject mode** to be `Based on the user's username`.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
- Set a `Strict` redirect URI to `https://espocrm.company/oauth-callback.php`.
- Select any available signing key.
- Under **Advanced protocol settings**, set **Subject mode** to **Based on the User's username**.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## EspoCRM configuration
To configure EspoCRM for OpenID Connect authentication, navigate to your instance and log in as an administrator. Then, navigate to **Administration** > **Authentication** and select the **OIDC method**. A panel allowing you to configure OIDC settings should appear.
1. Log in to EspoCRM as an administrator.
2. Navigate to **Administration** > **Authentication** and select **OIDC** as the authentication method.
3. Configure the following settings:
- **OIDC Client ID**: enter the Client ID from authentik.
- **OIDC Client Secret**: enter the Client Secret from authentik.
- **OIDC Authorization Endpoint**: `https://authentik.company/application/o/authorize/`
- **OIDC Token Endpoint**: `https://authentik.company/application/o/token/`
- **OIDC UserInfo Endpoint**: `https://authentik.company/application/o/userinfo/`
- **OIDC JSON Web Key Set Endpoint**: `https://authentik.company/application/o/<application_slug>/jwks/`
- **OIDC Scopes**: select or add `openid`, `profile`, and `email`.
- **OIDC Logout URL**: `https://authentik.company/application/o/<application_slug>/end-session/`
- **Allow OIDC login for admin users** _(optional)_: enable this setting if EspoCRM administrators should be able to log in with OIDC.
4. Confirm that the read-only **OIDC Authorization Redirect URI** matches `https://espocrm.company/oauth-callback.php`.
5. Save the configuration.
Configure the following fields:
- **Client ID**: The Client ID from authentik
- **Client Secret**: The Client Secret from authentik
- **Authorization Redirect URI**: `https://espocrm.company/oauth-callback.php`
- **Fallback Login**: Toggle this option if you wish to have the option to use EspoCRM's integrated login as a fallback.
- **Allow OIDC login for admin users**: Toggle this option if you wish to allow administrator users to log in with OIDC.
- **Authorization Endpoint**: `https://authentik.company/application/o/authorize`
- **Token Endpoint**: `https://authentik.company/application/o/token`
- **JSON Web Key Set Endpoint**: `https://authentik.company/application/o/<application_slug>/jwks`
- **Logout URL**: `https://authentik.company/application/o/<application_slug>/end_session`
:::info Existing Users
This configuration uses EspoCRM's username claim setting together with authentik's username-based subject mode. Existing EspoCRM users should have usernames that match their authentik usernames, unless you intentionally use a different claim mapping.
:::
## Configuration verification
To confirm that authentik is properly configured with EspoCRM, log out and log back in via authentik. Clicking the "Login" button on the homepage should redirect you to authentik.
To confirm that authentik is properly configured with EspoCRM, log out, open EspoCRM, click **Login**, and log back in via authentik.
## Resources

View File

@@ -8,7 +8,7 @@ support_level: community
> HedgeDoc lets you create real-time collaborative markdown notes.
>
> -- https://github.com/hedgedoc/hedgedoc
> -- https://hedgedoc.org/
## Preparation
@@ -29,32 +29,40 @@ To support the integration of HedgeDoc with authentik, you need to create an app
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
- Set a `Strict` redirect URI to `https://hedgedoc.company/auth/oauth2/callback`.
- Select any available signing key.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID** and **Client Secret** values because they will be required later.
- Set a `Strict` redirect URI to `https://hedgedoc.company/auth/oauth2/callback`.
- Select any available signing key.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## HedgeDoc configuration
You need to set the following `env` Variables for Docker based installations.
Set the following values:
Set the following environment variables in your HedgeDoc deployment:
```yaml
CMD_OAUTH2_PROVIDERNAME: "authentik"
CMD_OAUTH2_CLIENT_ID: "<Client ID from above>"
CMD_OAUTH2_CLIENT_SECRET: "<Client Secret from above>"
CMD_OAUTH2_CLIENT_ID: "<Client ID from authentik>"
CMD_OAUTH2_CLIENT_SECRET: "<Client Secret from authentik>"
CMD_OAUTH2_SCOPE: "openid email profile"
CMD_OAUTH2_USER_PROFILE_URL: "https://authentik.company/application/o/userinfo/"
CMD_OAUTH2_TOKEN_URL: "https://authentik.company/application/o/token/"
CMD_OAUTH2_AUTHORIZATION_URL: "https://authentik.company/application/o/authorize/"
CMD_OAUTH2_USER_PROFILE_ID_ATTR: "sub"
CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR: "preferred_username"
CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR: "name"
CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR: "email"
```
Restart HedgeDoc for the changes to take effect.
## Configuration verification
To confirm that authentik is properly configured with HedgeDoc, open HedgeDoc, select the **authentik** login option, and complete the authentik sign-in flow. A successful authentication should return you to HedgeDoc as a signed-in user.
## Resources
- [HedgeDoc documentation - Configuration](https://docs.hedgedoc.org/configuration/)
- [HedgeDoc documentation - OAuth](https://docs.hedgedoc.org/guides/auth/oauth/)

View File

@@ -45,9 +45,9 @@ Integrating Kanboard with authentik requires enabling the plugin system and inst
### Enable plugin management
To enable plugin management through the web interface, add the following line to your Kanboard configuration file, typically located at `/var/www/app/config.php`:
To enable plugin management through the web interface, add the following line to your Kanboard `config.php` file. Kanboard reads this file from the project root or the `data` folder. For Docker installations, the data folder is typically `/var/www/app/data/`.
```yaml
```php
define('PLUGIN_INSTALLER', true);
```
@@ -65,9 +65,9 @@ Then, restart your server to apply the updated configuration.
| **Callback URL** | `https://kanboard.company/oauth/callback` (prefilled) |
| **Client ID** | Client ID from authentik |
| **Client Secret** | Client secret from authentik |
| **Authorize URL** | `https://authentik.company/application/o/authorize` |
| **Token URL** | `https://authentik.company/application/o/token` |
| **User API URL** | `https://authentik.company/application/o/userinfo` |
| **Authorize URL** | `https://authentik.company/application/o/authorize/` |
| **Token URL** | `https://authentik.company/application/o/token/` |
| **User API URL** | `https://authentik.company/application/o/userinfo/` |
| **Scopes** | `openid profile email` |
| **Username Key** | `preferred_username` |
| **Name Key** | `name` |
@@ -79,7 +79,7 @@ Then, restart your server to apply the updated configuration.
## Configuration verification
To confirm that authentik is properly configured with Kanboard, log out and attempt to log back in by clicking **OAuth2 login**.
To confirm that authentik is properly configured with Kanboard, log out of Kanboard and log back in using the **OAuth2 login** option. You should be redirected to authentik for authentication and then redirected back to Kanboard.
## Resources

View File

@@ -78,7 +78,7 @@ The following `id` property mapping is optional. If omitted, Mattermost will gen
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, and the policy engine mode. Take note of the **Slug** value because it will be required later.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
@@ -155,7 +155,7 @@ To support the integration of Mattermost with authentik via SAML, you need to up
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair.
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair.
- **Application**: provide a descriptive name (e.g., `Mattermost`), an optional group for the type of application, and the policy engine mode. Take note of the **slug** as it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations:

View File

@@ -115,7 +115,7 @@ To connect to an existing Nextcloud user, set the `nextcloud_user_id` attribute
## Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
@@ -212,7 +212,7 @@ If you require [server side encryption](https://docs.nextcloud.com/server/latest
## Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- Note the application slug because it will be required later.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
@@ -344,7 +344,7 @@ This documentation lists only the settings that you need to change from their de
## Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **LDAP** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name) and the bind flow to use for this provider

View File

@@ -28,7 +28,7 @@ To support the integration of Planka with authentik, you need to create an appli
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.

View File

@@ -36,7 +36,7 @@ To support the integration of Vikunja with authentik, you need to create an appl
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.

View File

@@ -28,7 +28,7 @@ To support the integration of Wekan with authentik, you need to create an applic
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.

View File

@@ -105,7 +105,7 @@ To support the integration of AWS with authentik via the Classic IAM method, you
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name (e.g. `AWS`), an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **SAML Provider** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), and configure the following required settings:
@@ -265,7 +265,7 @@ To support the integration of AWS with authentik using OIDC, you need to create
### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name (e.g., `AWS-OIDC`), an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: Select OAuth2/OpenID Provider as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.

View File

@@ -47,7 +47,7 @@ To support the integration of AWS with authentik using SAML, you need to create
#### Create an application and provider in authentik
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
2. Navigate to **Applications** > **Applications** and click **New Application** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name (e.g. `AWS Identity Center`), an optional group for the type of application, the policy engine mode, and optional UI settings.
- Under **UI Settings**, set the **Launch URL** to the **AWS access portal sign-in URL** copied from AWS.
- **Choose a Provider type**: select **SAML Provider from metadata** as the provider type.

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