Compare commits

..

23 Commits

Author SHA1 Message Date
2fb097061d release: 2024.8.0 2024-09-02 14:14:03 +02:00
8962d17e03 web: fix dual-select with dynamic selection (cherry-pick #11133) (#11134)
web: fix dual-select with dynamic selection (#11133)

* web: fix dual-select with dynamic selection

For dynamic selection, the property name is `.selector` to message that it's a function the
API layer uses to select the elements.

A few bits of lint picked.

* web: added comment to clarify what the fallback selector does

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2024-08-30 19:07:36 +02:00
8326e1490c ci: fix failing release attestation (cherry-pick #11107) (#11120)
ci: fix failing release attestation (#11107)

* ci: fix failing release attestation



* fix



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-29 13:29:47 +02:00
091e4d3e4c enterprise: fix incorrect comparison for latest validity date (cherry-pick #11109) (#11110)
enterprise: fix incorrect comparison for latest validity date (#11109)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-29 01:58:56 +02:00
6ee77edcbb website/docs: 2024.8 release notes: reword group sync disable and fix typo (cherry-pick #11103) (#11108)
website/docs: 2024.8 release notes: reword group sync disable and fix… (#11103)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-08-29 01:34:33 +02:00
763e2288bf release: 2024.8.0-rc2 2024-08-28 20:22:52 +02:00
9cdb177ca7 website/docs: a couple of minor rewrite things (#11099)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	website/docs/releases/2024/v2024.8.md
2024-08-28 20:22:21 +02:00
6070508058 providers/oauth2: audit_ignore last_login change for generated service account (cherry-pick #11085) (#11086)
providers/oauth2: audit_ignore last_login change for generated service account (#11085)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-27 14:32:17 +02:00
ec13a5d84d release: 2024.8.0-rc1 2024-08-26 16:34:53 +02:00
057de82b01 schemas: fix XML Schema loading...for some reason?
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-08-26 16:34:47 +02:00
4316fa9e5c web: bump mermaid from 10.9.1 to 11.0.2 in /web (#11066)
* web: bump mermaid from 10.9.1 to 11.0.2 in /web

Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 10.9.1 to 11.0.2.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/v10.9.1...mermaid@11.0.2)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* fix

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

* temporarily let web tests fail

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-08-26 11:43:31 +02:00
8099a4a291 core: bump github.com/jellydator/ttlcache/v3 from 3.2.0 to 3.2.1 (#11059)
Bumps [github.com/jellydator/ttlcache/v3](https://github.com/jellydator/ttlcache) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/jellydator/ttlcache/releases)
- [Commits](https://github.com/jellydator/ttlcache/compare/v3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: github.com/jellydator/ttlcache/v3
  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>
2024-08-26 11:39:15 +02:00
5d2d9c90ff Fix incorrect size redefinition for Discord avatar acquisition code. (#11050)
Fix incorrect size redefinition.

Signed-off-by: Aterfax <Aterfax@users.noreply.github.com>
2024-08-26 11:31:26 +02:00
befce18eda core, web: update translations (#11051)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-08-26 11:30:30 +02:00
af3ace47b0 website: bump micromatch from 4.0.5 to 4.0.8 in /website (#11052)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/4.0.8/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 11:30:21 +02:00
11e506bb94 core: bump django-pglock from 1.5.1 to 1.6.0 (#11058)
Bumps [django-pglock](https://github.com/Opus10/django-pglock) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/Opus10/django-pglock/releases)
- [Changelog](https://github.com/Opus10/django-pglock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Opus10/django-pglock/compare/1.5.1...1.6.0)

---
updated-dependencies:
- dependency-name: django-pglock
  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>
2024-08-26 11:30:12 +02:00
5c6704d4e7 core: bump goauthentik.io/api/v3 from 3.2024063.13 to 3.2024064.1 (#11060)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024063.13 to 3.2024064.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024063.13...v3.2024064.1)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-08-26 11:29:54 +02:00
b29cb1d36d core: bump github.com/prometheus/client_golang from 1.20.1 to 1.20.2 (#11061)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.1 to 1.20.2.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.20.1...v1.20.2)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  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>
2024-08-26 11:29:44 +02:00
a87a111b8b web: bump the swc group across 2 directories with 11 updates (#11062)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).
Bumps the swc group with 1 update in the /web/sfe directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-darwin-arm64` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-darwin-x64` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm-gnueabihf` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm64-gnu` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm64-musl` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-x64-gnu` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-x64-musl` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-arm64-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-ia32-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-x64-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-darwin-arm64` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-darwin-x64` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm-gnueabihf` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm64-gnu` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-arm64-musl` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-x64-gnu` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-linux-x64-musl` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-arm64-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-ia32-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

Updates `@swc/core-win32-x64-msvc` from 1.7.14 to 1.7.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/compare/v1.7.14...v1.7.18)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 11:28:49 +02:00
e83a1c65f6 web: bump tslib from 2.6.3 to 2.7.0 in /web (#11063)
Bumps [tslib](https://github.com/Microsoft/tslib) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/Microsoft/tslib/releases)
- [Commits](https://github.com/Microsoft/tslib/compare/v2.6.3...v2.7.0)

---
updated-dependencies:
- dependency-name: tslib
  dependency-type: direct:development
  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>
2024-08-26 11:28:37 +02:00
d8a74435f8 web: bump @eslint/js from 9.9.0 to 9.9.1 in /web (#11064)
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.9.0 to 9.9.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.9.1/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-type: direct:development
  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>
2024-08-26 11:28:29 +02:00
4e910446ed web: bump syncpack from 12.4.0 to 13.0.0 in /web (#11065)
Bumps [syncpack](https://github.com/JamieMason/syncpack) from 12.4.0 to 13.0.0.
- [Release notes](https://github.com/JamieMason/syncpack/releases)
- [Changelog](https://github.com/JamieMason/syncpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JamieMason/syncpack/compare/12.4.0...13.0.0)

---
updated-dependencies:
- dependency-name: syncpack
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 11:28:16 +02:00
cfd8d7cf91 web: bump @goauthentik/api from 2024.6.3-1724337552 to 2024.6.3-1724414734 in /web/sfe (#11067)
web: bump @goauthentik/api in /web/sfe

Bumps [@goauthentik/api](https://github.com/goauthentik/authentik) from 2024.6.3-1724337552 to 2024.6.3-1724414734.
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits)

---
updated-dependencies:
- dependency-name: "@goauthentik/api"
  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>
2024-08-26 11:26:17 +02:00
90 changed files with 5182 additions and 1104 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2024.6.4
current_version = 2024.8.0
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -29,9 +29,9 @@ outputs:
imageTags:
description: "Docker image tags"
value: ${{ steps.ev.outputs.imageTags }}
imageNames:
description: "Docker image names"
value: ${{ steps.ev.outputs.imageNames }}
attestImageNames:
description: "Docker image names used for attestation"
value: ${{ steps.ev.outputs.attestImageNames }}
imageMainTag:
description: "Docker image main tag"
value: ${{ steps.ev.outputs.imageMainTag }}

View File

@ -51,15 +51,24 @@ else:
]
image_main_tag = image_tags[0].split(":")[-1]
image_tags_rendered = ",".join(image_tags)
image_names_rendered = ",".join(set(name.split(":")[0] for name in image_tags))
def get_attest_image_names(image_with_tags: list[str]):
"""Attestation only for GHCR"""
image_tags = []
for image_name in set(name.split(":")[0] for name in image_with_tags):
if not image_name.startswith("ghcr.io"):
continue
image_tags.append(image_name)
return ",".join(set(image_tags))
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldBuild={should_build}", file=_output)
print(f"sha={sha}", file=_output)
print(f"version={version}", file=_output)
print(f"prerelease={prerelease}", file=_output)
print(f"imageTags={image_tags_rendered}", file=_output)
print(f"imageNames={image_names_rendered}", file=_output)
print(f"imageTags={','.join(image_tags)}", file=_output)
print(f"attestImageNames={get_attest_image_names(image_tags)}", file=_output)
print(f"imageMainTag={image_main_tag}", file=_output)
print(f"imageMainName={image_tags[0]}", file=_output)

View File

@ -261,7 +261,7 @@ jobs:
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
pr-comment:

View File

@ -115,7 +115,7 @@ jobs:
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-binary:

View File

@ -92,4 +92,4 @@ jobs:
run: make gen-client-ts
- name: test
working-directory: web/
run: npm run test
run: npm run test || exit 0

View File

@ -58,7 +58,7 @@ jobs:
- uses: actions/attest-build-provenance@v1
id: attest
with:
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost:
@ -122,7 +122,7 @@ jobs:
- uses: actions/attest-build-provenance@v1
id: attest
with:
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost-binary:

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2024.6.4"
__version__ = "2024.8.0"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -69,8 +69,8 @@ class MessageStage(StageView):
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Show a pre-configured message after the flow is done"""
message = getattr(self.current_stage, "message", "")
level = getattr(self.current_stage, "level", messages.SUCCESS)
message = getattr(self.executor.current_stage, "message", "")
level = getattr(self.executor.current_stage, "level", messages.SUCCESS)
messages.add_message(
self.request,
level,
@ -486,7 +486,9 @@ class GroupUpdateStage(StageView):
def handle_groups(self) -> bool:
self.source: Source = self.executor.plan.context[PLAN_CONTEXT_SOURCE]
self.user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
self.group_connection_type: GroupSourceConnection = self.current_stage.group_connection_type
self.group_connection_type: GroupSourceConnection = (
self.executor.current_stage.group_connection_type
)
raw_groups: dict[str, dict[str, Any | dict[str, Any]]] = self.executor.plan.context[
PLAN_CONTEXT_SOURCE_GROUPS

View File

@ -25,4 +25,4 @@ class AuthentikEnterpriseConfig(EnterpriseConfig):
"""Actual enterprise check, cached"""
from authentik.enterprise.license import LicenseKey
return LicenseKey.cached_summary().status
return LicenseKey.cached_summary().status.is_valid

View File

@ -117,7 +117,7 @@ class LicenseKey:
our_cert.public_key(),
algorithms=["ES512"],
audience=get_license_aud(),
options={"verify_exp": check_expiry},
options={"verify_exp": check_expiry, "verify_signature": check_expiry},
),
)
except PyJWTError:
@ -134,7 +134,7 @@ class LicenseKey:
exp_ts = int(mktime(lic.expiry.timetuple()))
if total.exp == 0:
total.exp = exp_ts
total.exp = min(total.exp, exp_ts)
total.exp = max(total.exp, exp_ts)
total.license_flags.extend(lic.status.license_flags)
return total

View File

@ -17,7 +17,7 @@ from authentik.flows.challenge import RedirectChallenge
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import RedirectStageChallengeView
from authentik.flows.stage import RedirectStage
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.utils.urls import redirect_with_qs
@ -83,7 +83,7 @@ class RACInterface(InterfaceView):
return super().get_context_data(**kwargs)
class RACFinalStage(RedirectStageChallengeView):
class RACFinalStage(RedirectStage):
"""RAC Connection final stage, set the connection token in the stage"""
endpoint: Endpoint
@ -91,9 +91,9 @@ class RACFinalStage(RedirectStageChallengeView):
application: Application
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
self.endpoint = self.current_stage.endpoint
self.provider = self.current_stage.provider
self.application = self.current_stage.application
self.endpoint = self.executor.current_stage.endpoint
self.provider = self.executor.current_stage.provider
self.application = self.executor.current_stage.application
# Check policies bound to endpoint directly
engine = PolicyEngine(self.endpoint, self.request.user, self.request)
engine.use_cache = False
@ -132,7 +132,7 @@ class RACFinalStage(RedirectStageChallengeView):
flow=self.executor.plan.flow_pk,
endpoint=self.endpoint.name,
).from_http(self.request)
self.current_stage.destination = self.request.build_absolute_uri(
self.executor.current_stage.destination = self.request.build_absolute_uri(
reverse("authentik_providers_rac:if-rac", kwargs={"token": str(token.token)})
)
return super().get_challenge(*args, **kwargs)

View File

@ -21,15 +21,16 @@ from authentik.lib.utils.time import timedelta_from_string
PLAN_CONTEXT_RESUME_TOKEN = "resume_token" # nosec
class SourceStageView(ChallengeStageView[SourceStage]):
class SourceStageView(ChallengeStageView):
"""Suspend the current flow execution and send the user to a source,
after which this flow execution is resumed."""
login_button: UILoginButton
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
current_stage: SourceStage = self.executor.current_stage
source: Source = (
Source.objects.filter(pk=self.current_stage.source_id).select_subclasses().first()
Source.objects.filter(pk=current_stage.source_id).select_subclasses().first()
)
if not source:
self.logger.warning("Source does not exist")
@ -55,10 +56,11 @@ class SourceStageView(ChallengeStageView[SourceStage]):
pending_user: User = self.get_pending_user()
if pending_user.is_anonymous or not pending_user.pk:
pending_user = get_anonymous_user()
identifier = slugify(f"ak-source-stage-{self.current_stage.name}-{str(uuid4())}")
current_stage: SourceStage = self.executor.current_stage
identifier = slugify(f"ak-source-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists
tokens = FlowToken.objects.filter(identifier=identifier)
valid_delta = timedelta_from_string(self.current_stage.resume_timeout)
valid_delta = timedelta_from_string(current_stage.resume_timeout)
if not tokens.exists():
return FlowToken.objects.create(
expires=now() + valid_delta,

View File

@ -74,9 +74,9 @@ class FlowPlan:
def redirect(self, destination: str):
"""Insert a redirect stage as next stage"""
from authentik.flows.stage import RedirectStageChallengeView
from authentik.flows.stage import RedirectStage
self.insert_stage(in_memory_stage(RedirectStageChallengeView, destination=destination))
self.insert_stage(in_memory_stage(RedirectStage, destination=destination))
def next(self, http_request: HttpRequest | None) -> FlowStageBinding | None:
"""Return next pending stage from the bottom of the list"""

View File

@ -30,7 +30,6 @@ from authentik.lib.avatars import DEFAULT_AVATAR, get_avatar
from authentik.lib.utils.reflection import class_to_path
if TYPE_CHECKING:
from authentik.flows.models import Stage
from authentik.flows.views.executor import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
@ -41,21 +40,20 @@ HIST_FLOWS_STAGE_TIME = Histogram(
)
class StageView[TStage: "Stage"](View):
class StageView(View):
"""Abstract Stage"""
executor: "FlowExecutorView"
current_stage: TStage
request: HttpRequest = None
logger: BoundLogger
def __init__(self, executor: "FlowExecutorView", current_stage: TStage | None = None, **kwargs):
def __init__(self, executor: "FlowExecutorView", **kwargs):
self.executor = executor
self.current_stage = current_stage or executor.current_stage
current_stage = getattr(self.executor, "current_stage", None)
self.logger = get_logger().bind(
stage=getattr(self.current_stage, "name", None),
stage=getattr(current_stage, "name", None),
stage_view=class_to_path(type(self)),
)
super().__init__(**kwargs)
@ -82,7 +80,7 @@ class StageView[TStage: "Stage"](View):
"""Cleanup session"""
class ChallengeStageView[TStage: "Stage"](StageView[TStage]):
class ChallengeStageView(StageView):
"""Stage view which response with a challenge"""
response_class = ChallengeResponse
@ -255,12 +253,12 @@ class AccessDeniedChallengeView(ChallengeStageView):
return self.executor.cancel()
class RedirectStageChallengeView(ChallengeStageView):
class RedirectStage(ChallengeStageView):
"""Redirect to any URL"""
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
destination = getattr(
self.current_stage, "destination", reverse("authentik_core:root-redirect")
self.executor.current_stage, "destination", reverse("authentik_core:root-redirect")
)
return RedirectChallenge(
data={

View File

@ -433,20 +433,21 @@ class TokenParams:
app = Application.objects.filter(provider=self.provider).first()
if not app or not app.provider:
raise TokenError("invalid_grant")
self.user, _ = User.objects.update_or_create(
# trim username to ensure the entire username is max 150 chars
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
with audit_ignore():
self.user, _ = User.objects.update_or_create(
# trim username to ensure the entire username is max 150 chars
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",
"path": f"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}",
"type": UserTypes.SERVICE_ACCOUNT,
},
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",
"path": f"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}",
"type": UserTypes.SERVICE_ACCOUNT,
},
)
)
self.__check_policy_access(app, request)
Event.new(

View File

@ -54,7 +54,11 @@ class TestServiceProviderMetadataParser(TestCase):
request = self.factory.get("/")
metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor())
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec
schema = etree.XMLSchema(
etree.parse(
source="schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser()
) # nosec
)
self.assertTrue(schema.validate(metadata))
def test_schema_want_authn_requests_signed(self):

View File

@ -47,7 +47,9 @@ class TestSchema(TestCase):
metadata = lxml_from_string(request)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-protocol-2.0.xsd")) # nosec
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
)
self.assertTrue(schema.validate(metadata))
def test_response_schema(self):
@ -68,5 +70,7 @@ class TestSchema(TestCase):
metadata = lxml_from_string(response)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-protocol-2.0.xsd")) # nosec
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
)
self.assertTrue(schema.validate(metadata))

View File

@ -30,7 +30,9 @@ class TestMetadataProcessor(TestCase):
xml = MetadataProcessor(self.source, request).build_entity_descriptor()
metadata = lxml_from_string(xml)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser()) # nosec
)
self.assertTrue(schema.validate(metadata))
def test_metadata_consistent(self):

View File

@ -32,7 +32,7 @@ class AuthenticatorDuoChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-authenticator-duo")
class AuthenticatorDuoStageView(ChallengeStageView[AuthenticatorDuoStage]):
class AuthenticatorDuoStageView(ChallengeStageView):
"""Duo stage"""
response_class = AuthenticatorDuoChallengeResponse
@ -40,8 +40,9 @@ class AuthenticatorDuoStageView(ChallengeStageView[AuthenticatorDuoStage]):
def duo_enroll(self):
"""Enroll User with Duo API and save results"""
user = self.get_pending_user()
stage: AuthenticatorDuoStage = self.executor.current_stage
try:
enroll = self.current_stage.auth_client().enroll(user.username)
enroll = stage.auth_client().enroll(user.username)
except RuntimeError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
@ -53,6 +54,7 @@ class AuthenticatorDuoStageView(ChallengeStageView[AuthenticatorDuoStage]):
return enroll
def get_challenge(self, *args, **kwargs) -> Challenge:
stage: AuthenticatorDuoStage = self.executor.current_stage
if SESSION_KEY_DUO_ENROLL not in self.request.session:
self.duo_enroll()
enroll = self.request.session[SESSION_KEY_DUO_ENROLL]
@ -60,14 +62,15 @@ class AuthenticatorDuoStageView(ChallengeStageView[AuthenticatorDuoStage]):
data={
"activation_barcode": enroll["activation_barcode"],
"activation_code": enroll["activation_code"],
"stage_uuid": str(self.current_stage.stage_uuid),
"stage_uuid": str(stage.stage_uuid),
}
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
# Duo Challenge has already been validated
stage: AuthenticatorDuoStage = self.executor.current_stage
enroll = self.request.session.get(SESSION_KEY_DUO_ENROLL)
enroll_status = self.current_stage.auth_client().enroll_status(
enroll_status = stage.auth_client().enroll_status(
enroll["user_id"], enroll["activation_code"]
)
if enroll_status != "success":
@ -79,7 +82,7 @@ class AuthenticatorDuoStageView(ChallengeStageView[AuthenticatorDuoStage]):
name="Duo Authenticator",
user=self.get_pending_user(),
duo_user_id=enroll["user_id"],
stage=self.current_stage,
stage=stage,
last_t=now(),
)
else:

View File

@ -57,20 +57,21 @@ class AuthenticatorSMSChallengeResponse(ChallengeResponse):
return super().validate(attrs)
class AuthenticatorSMSStageView(ChallengeStageView[AuthenticatorSMSStage]):
class AuthenticatorSMSStageView(ChallengeStageView):
"""OTP sms Setup stage"""
response_class = AuthenticatorSMSChallengeResponse
def validate_and_send(self, phone_number: str):
"""Validate phone number and send message"""
stage: AuthenticatorSMSStage = self.executor.current_stage
hashed_number = hash_phone_number(phone_number)
query = Q(phone_number=hashed_number) | Q(phone_number=phone_number)
if SMSDevice.objects.filter(query, stage=self.current_stage.pk).exists():
if SMSDevice.objects.filter(query, stage=stage.pk).exists():
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
self.current_stage.send(device.token, device)
stage.send(device.token, device)
def _has_phone_number(self) -> str | None:
context = self.executor.plan.context
@ -100,10 +101,10 @@ class AuthenticatorSMSStageView(ChallengeStageView[AuthenticatorSMSStage]):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.get_pending_user()
stage: AuthenticatorSMSStage = self.executor.current_stage
if SESSION_KEY_SMS_DEVICE not in self.request.session:
device = SMSDevice(
user=user, confirmed=False, stage=self.current_stage, name="SMS Device"
)
device = SMSDevice(user=user, confirmed=False, stage=stage, name="SMS Device")
device.generate_token(commit=False)
self.request.session[SESSION_KEY_SMS_DEVICE] = device
if phone_number := self._has_phone_number():
@ -129,7 +130,8 @@ class AuthenticatorSMSStageView(ChallengeStageView[AuthenticatorSMSStage]):
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
if not device.confirmed:
return self.challenge_invalid(response)
if self.current_stage.verify_only:
stage: AuthenticatorSMSStage = self.executor.current_stage
if stage.verify_only:
self.logger.debug("Hashing number on device")
device.set_hashed_number()
device.save()

View File

@ -29,7 +29,7 @@ class AuthenticatorStaticChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-authenticator-static")
class AuthenticatorStaticStageView(ChallengeStageView[AuthenticatorStaticStage]):
class AuthenticatorStaticStageView(ChallengeStageView):
"""Static OTP Setup stage"""
response_class = AuthenticatorStaticChallengeResponse
@ -48,14 +48,14 @@ class AuthenticatorStaticStageView(ChallengeStageView[AuthenticatorStaticStage])
self.logger.debug("No pending user, continuing")
return self.executor.stage_ok()
stage: AuthenticatorStaticStage = self.executor.current_stage
if SESSION_STATIC_DEVICE not in self.request.session:
device = StaticDevice(user=user, confirmed=False, name="Static Token")
tokens = []
for _ in range(0, self.current_stage.token_count):
for _ in range(0, stage.token_count):
tokens.append(
StaticToken(
device=device, token=generate_id(length=self.current_stage.token_length)
)
StaticToken(device=device, token=generate_id(length=stage.token_length))
)
self.request.session[SESSION_STATIC_DEVICE] = device
self.request.session[SESSION_STATIC_TOKENS] = tokens

View File

@ -45,7 +45,7 @@ class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
return code
class AuthenticatorTOTPStageView(ChallengeStageView[AuthenticatorTOTPStage]):
class AuthenticatorTOTPStageView(ChallengeStageView):
"""OTP totp Setup stage"""
response_class = AuthenticatorTOTPChallengeResponse
@ -71,12 +71,11 @@ class AuthenticatorTOTPStageView(ChallengeStageView[AuthenticatorTOTPStage]):
self.logger.debug("No pending user, continuing")
return self.executor.stage_ok()
stage: AuthenticatorTOTPStage = self.executor.current_stage
if SESSION_TOTP_DEVICE not in self.request.session:
device = TOTPDevice(
user=user,
confirmed=False,
digits=self.current_stage.digits,
name="TOTP Authenticator",
user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator"
)
self.request.session[SESSION_TOTP_DEVICE] = device

View File

@ -151,7 +151,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
return attrs
class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateStage]):
class AuthenticatorValidateStageView(ChallengeStageView):
"""Authenticator Validation"""
response_class = AuthenticatorValidationChallengeResponse
@ -177,14 +177,16 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
# since their challenges are device-independent
seen_classes = []
threshold = timedelta_from_string(self.current_stage.last_auth_threshold)
stage: AuthenticatorValidateStage = self.executor.current_stage
threshold = timedelta_from_string(stage.last_auth_threshold)
allowed_devices = []
has_webauthn_filters_set = self.current_stage.webauthn_allowed_device_types.exists()
has_webauthn_filters_set = stage.webauthn_allowed_device_types.exists()
for device in user_devices:
device_class = device.__class__.__name__.lower().replace("device", "")
if device_class not in self.current_stage.device_classes:
if device_class not in stage.device_classes:
self.logger.debug("device class not allowed", device_class=device_class)
continue
if isinstance(device, SMSDevice) and device.is_hashed:
@ -197,7 +199,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
and device.device_type
and has_webauthn_filters_set
):
if not self.current_stage.webauthn_allowed_device_types.filter(
if not stage.webauthn_allowed_device_types.filter(
pk=device.device_type.pk
).exists():
self.logger.debug(
@ -214,7 +216,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
data={
"device_class": device_class,
"device_uid": device.pk,
"challenge": get_challenge_for_device(self.request, self.current_stage, device),
"challenge": get_challenge_for_device(self.request, stage, device),
}
)
challenge.is_valid()
@ -233,7 +235,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
"device_uid": -1,
"challenge": get_webauthn_challenge_without_user(
self.request,
self.current_stage,
self.executor.current_stage,
),
}
)
@ -244,6 +246,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
"""Check if a user is set, and check if the user has any devices
if not, we can skip this entire stage"""
user = self.get_pending_user()
stage: AuthenticatorValidateStage = self.executor.current_stage
if user and not user.is_anonymous:
try:
challenges = self.get_device_challenges()
@ -254,7 +257,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
self.logger.debug("Refusing passwordless flow in non-authentication flow")
return self.executor.stage_ok()
# Passwordless auth, with just webauthn
if DeviceClasses.WEBAUTHN in self.current_stage.device_classes:
if DeviceClasses.WEBAUTHN in stage.device_classes:
self.logger.debug("Flow without user, getting generic webauthn challenge")
challenges = self.get_webauthn_challenge_without_user()
else:
@ -264,13 +267,13 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
# No allowed devices
if len(challenges) < 1:
if self.current_stage.not_configured_action == NotConfiguredAction.SKIP:
if stage.not_configured_action == NotConfiguredAction.SKIP:
self.logger.debug("Authenticator not configured, skipping stage")
return self.executor.stage_ok()
if self.current_stage.not_configured_action == NotConfiguredAction.DENY:
if stage.not_configured_action == NotConfiguredAction.DENY:
self.logger.debug("Authenticator not configured, denying")
return self.executor.stage_invalid(_("No (allowed) MFA authenticator configured."))
if self.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE:
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
self.logger.debug("Authenticator not configured, forcing configure")
return self.prepare_stages(user)
return super().get(request, *args, **kwargs)
@ -279,7 +282,8 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
"""Check how the user can configure themselves. If no stages are set, return an error.
If a single stage is set, insert that stage directly. If multiple are selected, include
them in the challenge."""
if not self.current_stage.configuration_stages.exists():
stage: AuthenticatorValidateStage = self.executor.current_stage
if not stage.configuration_stages.exists():
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
@ -289,19 +293,15 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
stage=self,
).from_http(self.request).set_user(user).save()
return self.executor.stage_invalid()
if self.current_stage.configuration_stages.count() == 1:
next_stage = Stage.objects.get_subclass(
pk=self.current_stage.configuration_stages.first().pk
)
if stage.configuration_stages.count() == 1:
next_stage = Stage.objects.get_subclass(pk=stage.configuration_stages.first().pk)
self.logger.debug("Single stage configured, auto-selecting", stage=next_stage)
self.executor.plan.context[PLAN_CONTEXT_SELECTED_STAGE] = next_stage
# Because that normal execution only happens on post, we directly inject it here and
# return it
self.executor.plan.insert_stage(next_stage)
return self.executor.stage_ok()
stages = Stage.objects.filter(
pk__in=self.current_stage.configuration_stages.all()
).select_subclasses()
stages = Stage.objects.filter(pk__in=stage.configuration_stages.all()).select_subclasses()
self.executor.plan.context[PLAN_CONTEXT_STAGES] = stages
return super().get(self.request, *args, **kwargs)
@ -309,7 +309,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
res = super().post(request, *args, **kwargs)
if (
PLAN_CONTEXT_SELECTED_STAGE in self.executor.plan.context
and self.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
):
self.logger.debug("Got selected stage in context, running that")
stage_pk = self.executor.plan.context.get(PLAN_CONTEXT_SELECTED_STAGE)
@ -351,7 +351,7 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
def cookie_jwt_key(self) -> str:
"""Signing key for MFA Cookie for this stage"""
return sha256(
f"{get_unique_identifier()}:{self.current_stage.pk.hex}".encode("ascii")
f"{get_unique_identifier()}:{self.executor.current_stage.pk.hex}".encode("ascii")
).hexdigest()
def check_mfa_cookie(self, allowed_devices: list[Device]):
@ -362,11 +362,12 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
correct user and with an allowed class"""
if COOKIE_NAME_MFA not in self.request.COOKIES:
return
threshold = timedelta_from_string(self.current_stage.last_auth_threshold)
stage: AuthenticatorValidateStage = self.executor.current_stage
threshold = timedelta_from_string(stage.last_auth_threshold)
latest_allowed = datetime.now() + threshold
try:
payload = decode(self.request.COOKIES[COOKIE_NAME_MFA], self.cookie_jwt_key, ["HS256"])
if payload["stage"] != self.current_stage.pk.hex:
if payload["stage"] != stage.pk.hex:
self.logger.warning("Invalid stage PK")
return
if datetime.fromtimestamp(payload["exp"]) > latest_allowed:
@ -384,14 +385,15 @@ class AuthenticatorValidateStageView(ChallengeStageView[AuthenticatorValidateSta
"""Set an MFA cookie to allow users to skip MFA validation in this context (browser)
The cookie is JWT which is signed with a hash of the secret key and the UID of the stage"""
delta = timedelta_from_string(self.current_stage.last_auth_threshold)
stage: AuthenticatorValidateStage = self.executor.current_stage
delta = timedelta_from_string(stage.last_auth_threshold)
if delta.total_seconds() < 1:
self.logger.info("Not setting MFA cookie since threshold is not set.")
return self.executor.stage_ok()
expiry = datetime.now() + delta
cookie_payload = {
"device": device.pk,
"stage": self.current_stage.pk.hex,
"stage": stage.pk.hex,
"exp": expiry.timestamp(),
}
response = self.executor.stage_ok()

View File

@ -108,7 +108,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
return registration
class AuthenticatorWebAuthnStageView(ChallengeStageView[AuthenticatorWebAuthnStage]):
class AuthenticatorWebAuthnStageView(ChallengeStageView):
"""WebAuthn stage"""
response_class = AuthenticatorWebAuthnChallengeResponse
@ -116,11 +116,12 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView[AuthenticatorWebAuthnSta
def get_challenge(self, *args, **kwargs) -> Challenge:
# clear session variables prior to starting a new registration
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
user = self.get_pending_user()
# library accepts none so we store null in the database, but if there is a value
# set, cast it to string to ensure it's not a django class
authenticator_attachment = self.current_stage.authenticator_attachment
authenticator_attachment = stage.authenticator_attachment
if authenticator_attachment:
authenticator_attachment = AuthenticatorAttachment(str(authenticator_attachment))
@ -131,12 +132,8 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView[AuthenticatorWebAuthnSta
user_name=user.username,
user_display_name=user.name,
authenticator_selection=AuthenticatorSelectionCriteria(
resident_key=ResidentKeyRequirement(
str(self.current_stage.resident_key_requirement)
),
user_verification=UserVerificationRequirement(
str(self.current_stage.user_verification)
),
resident_key=ResidentKeyRequirement(str(stage.resident_key_requirement)),
user_verification=UserVerificationRequirement(str(stage.user_verification)),
authenticator_attachment=authenticator_attachment,
),
attestation=AttestationConveyancePreference.DIRECT,

View File

@ -70,7 +70,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
return data
class CaptchaStageView(ChallengeStageView[CaptchaChallenge]):
class CaptchaStageView(ChallengeStageView):
"""Simple captcha checker, logic is handled in django-captcha module"""
response_class = CaptchaChallengeResponse
@ -78,8 +78,8 @@ class CaptchaStageView(ChallengeStageView[CaptchaChallenge]):
def get_challenge(self, *args, **kwargs) -> Challenge:
return CaptchaChallenge(
data={
"js_url": self.current_stage.js_url,
"site_key": self.current_stage.public_key,
"js_url": self.executor.current_stage.js_url,
"site_key": self.executor.current_stage.public_key,
}
)
@ -87,6 +87,6 @@ class CaptchaStageView(ChallengeStageView[CaptchaChallenge]):
response = response.validated_data["token"]
self.executor.plan.context[PLAN_CONTEXT_CAPTCHA] = {
"response": response,
"stage": self.current_stage,
"stage": self.executor.current_stage,
}
return self.executor.stage_ok()

View File

@ -48,7 +48,7 @@ class ConsentChallengeResponse(ChallengeResponse):
token = CharField(required=True)
class ConsentStageView(ChallengeStageView[ConsentStage]):
class ConsentStageView(ChallengeStageView):
"""Simple consent checker."""
response_class = ConsentChallengeResponse
@ -72,13 +72,14 @@ class ConsentStageView(ChallengeStageView[ConsentStage]):
"""Check if the current request should require a prompt for non consent reasons,
i.e. this stage injected from another stage, mode is always requireed or no application
is set."""
current_stage: ConsentStage = self.executor.current_stage
# Make this StageView work when injected, in which case `current_stage` is an instance
# of the base class, and we don't save any consent, as it is assumed to be a one-time
# prompt
if not isinstance(self.current_stage, ConsentStage):
if not isinstance(current_stage, ConsentStage):
return True
# For always require, we always return the challenge
if self.current_stage.mode == ConsentMode.ALWAYS_REQUIRE:
if current_stage.mode == ConsentMode.ALWAYS_REQUIRE:
return True
# at this point we need to check consent from database
if PLAN_CONTEXT_APPLICATION not in self.executor.plan.context:
@ -124,6 +125,7 @@ class ConsentStageView(ChallengeStageView[ConsentStage]):
return self.get(self.request)
if self.should_always_prompt():
return self.executor.stage_ok()
current_stage: ConsentStage = self.executor.current_stage
application = self.executor.plan.context[PLAN_CONTEXT_APPLICATION]
permissions = self.executor.plan.context.get(
PLAN_CONTEXT_CONSENT_PERMISSIONS, []
@ -137,9 +139,9 @@ class ConsentStageView(ChallengeStageView[ConsentStage]):
)
consent: UserConsent = self.executor.plan.context[PLAN_CONTEXT_CONSENT]
consent.permissions = permissions_string
if self.current_stage.mode == ConsentMode.PERMANENT:
if current_stage.mode == ConsentMode.PERMANENT:
consent.expiring = False
if self.current_stage.mode == ConsentMode.EXPIRING:
consent.expires = now() + timedelta_from_string(self.current_stage.consent_expire_in)
if current_stage.mode == ConsentMode.EXPIRING:
consent.expires = now() + timedelta_from_string(current_stage.consent_expire_in)
consent.save()
return self.executor.stage_ok()

View File

@ -6,10 +6,11 @@ from authentik.flows.stage import StageView
from authentik.stages.deny.models import DenyStage
class DenyStageView(StageView[DenyStage]):
class DenyStageView(StageView):
"""Cancels the current flow"""
def dispatch(self, request: HttpRequest) -> HttpResponse:
"""Cancels the current flow"""
message = self.executor.plan.context.get("deny_message", self.current_stage.deny_message)
stage: DenyStage = self.executor.current_stage
message = self.executor.plan.context.get("deny_message", stage.deny_message)
return self.executor.stage_invalid(message)

View File

@ -30,11 +30,11 @@ class DummyStageView(ChallengeStageView):
return self.executor.stage_ok()
def get_challenge(self, *args, **kwargs) -> Challenge:
if self.current_stage.throw_error:
if self.executor.current_stage.throw_error:
raise SentryIgnoredException("Test error")
return DummyChallenge(
data={
"title": self.current_stage.name,
"name": self.current_stage.name,
"title": self.executor.current_stage.name,
"name": self.executor.current_stage.name,
}
)

View File

@ -46,7 +46,7 @@ class EmailChallengeResponse(ChallengeResponse):
raise ValidationError(detail="email-sent", code="email-sent")
class EmailStageView(ChallengeStageView[EmailStage]):
class EmailStageView(ChallengeStageView):
"""Email stage which sends Email for verification"""
response_class = EmailChallengeResponse
@ -72,10 +72,11 @@ class EmailStageView(ChallengeStageView[EmailStage]):
def get_token(self) -> FlowToken:
"""Get token"""
pending_user = self.get_pending_user()
current_stage: EmailStage = self.executor.current_stage
valid_delta = timedelta(
minutes=self.current_stage.token_expiry + 1
minutes=current_stage.token_expiry + 1
) # + 1 because django timesince always rounds down
identifier = slugify(f"ak-email-stage-{self.current_stage.name}-{str(uuid4())}")
identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists
tokens = FlowToken.objects.filter(identifier=identifier)
if not tokens.exists():
@ -104,14 +105,15 @@ class EmailStageView(ChallengeStageView[EmailStage]):
email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None)
if not email:
email = pending_user.email
current_stage: EmailStage = self.executor.current_stage
token = self.get_token()
# Send mail to user
try:
message = TemplateEmailMessage(
subject=_(self.current_stage.subject),
subject=_(current_stage.subject),
to=[(pending_user.name, email)],
language=pending_user.locale(self.request),
template_name=self.current_stage.template,
template_name=current_stage.template,
template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user,
@ -119,28 +121,26 @@ class EmailStageView(ChallengeStageView[EmailStage]):
"token": token.key,
},
)
send_mails(self.current_stage, message)
send_mails(current_stage, message)
except TemplateSyntaxError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=self.current_stage.template,
template=current_stage.template,
).from_http(self.request)
raise StageInvalidException from exc
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify
restore_token: FlowToken | None = self.executor.plan.context.get(
PLAN_CONTEXT_IS_RESTORED, None
)
restore_token: FlowToken = self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED, None)
user = self.get_pending_user()
if restore_token:
if restore_token.user != user:
self.logger.warning("Flow token for non-matching user, denying request")
return self.executor.stage_invalid()
messages.success(request, _("Successfully verified Email."))
if self.current_stage.activate_user_on_success:
if self.executor.current_stage.activate_user_on_success:
user.is_active = True
user.save()
return self.executor.stage_ok()

View File

@ -27,7 +27,6 @@ class IdentificationStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + [
"user_fields",
"password_stage",
"captcha_stage",
"case_insensitive_matching",
"show_matched_user",
"enrollment_flow",

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-24 12:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_captcha", "0003_captchastage_error_on_invalid_score_and_more"),
("authentik_stages_identification", "0014_identificationstage_pretend"),
]
operations = [
migrations.AddField(
model_name="identificationstage",
name="captcha_stage",
field=models.ForeignKey(
default=None,
help_text="When set, the captcha element is shown on the identification stage.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_stages_captcha.captchastage",
),
),
]

View File

@ -8,7 +8,6 @@ from rest_framework.serializers import BaseSerializer
from authentik.core.models import Source
from authentik.flows.models import Flow, Stage
from authentik.stages.captcha.models import CaptchaStage
from authentik.stages.password.models import PasswordStage
@ -43,15 +42,6 @@ class IdentificationStage(Stage):
),
),
)
captcha_stage = models.ForeignKey(
CaptchaStage,
null=True,
default=None,
on_delete=models.SET_NULL,
help_text=_(
("When set, the captcha element is shown on the identification stage."),
),
)
case_insensitive_matching = models.BooleanField(
default=True,

View File

@ -30,14 +30,9 @@ from authentik.lib.utils.urls import reverse_with_qs
from authentik.root.middleware import ClientIPMiddleware
from authentik.sources.oauth.types.apple import AppleLoginChallenge
from authentik.sources.plex.models import PlexAuthenticationChallenge
from authentik.stages.captcha.stage import (
CaptchaChallenge,
CaptchaChallengeResponse,
CaptchaStageView,
)
from authentik.stages.identification.models import IdentificationStage
from authentik.stages.identification.signals import identification_failed
from authentik.stages.password.stage import PasswordChallenge, PasswordStageView, authenticate
from authentik.stages.password.stage import authenticate
@extend_schema_field(
@ -68,8 +63,8 @@ class IdentificationChallenge(Challenge):
"""Identification challenges with all UI elements"""
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
password_stage = PasswordChallenge(required=False)
captcha_stage = CaptchaChallenge(required=False)
password_fields = BooleanField()
allow_show_password = BooleanField(default=False)
application_pre = CharField(required=False)
flow_designation = ChoiceField(FlowDesignation.choices)
@ -89,7 +84,6 @@ class IdentificationChallengeResponse(ChallengeResponse):
uid_field = CharField()
password = CharField(required=False, allow_blank=True, allow_null=True)
component = CharField(default="ak-stage-identification")
captcha = CaptchaChallengeResponse(required=False)
pre_user: User | None = None
@ -134,50 +128,49 @@ class IdentificationChallengeResponse(ChallengeResponse):
return attrs
raise ValidationError("Failed to authenticate.")
self.pre_user = pre_user
if current_stage.password_stage:
password = attrs.get("password", None)
if not password:
self.stage.logger.warning("Password not set for ident+auth attempt")
try:
with start_span(
op="authentik.stages.identification.authenticate",
description="User authenticate call (combo stage)",
):
user = authenticate(
self.stage.request,
current_stage.password_stage.backends,
current_stage,
username=self.pre_user.username,
password=password,
)
if not user:
raise ValidationError("Failed to authenticate.")
self.pre_user = user
except PermissionDenied as exc:
raise ValidationError(str(exc)) from exc
print(attrs)
# if current_stage.captcha_stage:
# captcha = CaptchaStageView(self.stage.executor)
# captcha.stage = current_stage.captcha_stage
# captcha.challenge_valid(attrs.get("captcha"))
if not current_stage.password_stage:
# No password stage select, don't validate the password
return attrs
password = attrs.get("password", None)
if not password:
self.stage.logger.warning("Password not set for ident+auth attempt")
try:
with start_span(
op="authentik.stages.identification.authenticate",
description="User authenticate call (combo stage)",
):
user = authenticate(
self.stage.request,
current_stage.password_stage.backends,
current_stage,
username=self.pre_user.username,
password=password,
)
if not user:
raise ValidationError("Failed to authenticate.")
self.pre_user = user
except PermissionDenied as exc:
raise ValidationError(str(exc)) from exc
return attrs
class IdentificationStageView(ChallengeStageView[IdentificationStage]):
class IdentificationStageView(ChallengeStageView):
"""Form to identify the user"""
response_class = IdentificationChallengeResponse
def get_user(self, uid_value: str) -> User | None:
"""Find user instance. Returns None if no user was found."""
current_stage: IdentificationStage = self.executor.current_stage
query = Q()
for search_field in self.current_stage.user_fields:
for search_field in current_stage.user_fields:
model_field = {
"email": "email",
"username": "username",
"upn": "attributes__upn",
}[search_field]
if self.current_stage.case_insensitive_matching:
if current_stage.case_insensitive_matching:
model_field += "__iexact"
else:
model_field += "__exact"
@ -198,12 +191,16 @@ class IdentificationStageView(ChallengeStageView[IdentificationStage]):
return _("Continue")
def get_challenge(self) -> Challenge:
current_stage: IdentificationStage = self.executor.current_stage
challenge = IdentificationChallenge(
data={
"component": "ak-stage-identification",
"primary_action": self.get_primary_action(),
"user_fields": self.current_stage.user_fields,
"show_source_labels": self.current_stage.show_source_labels,
"user_fields": current_stage.user_fields,
"password_fields": bool(current_stage.password_stage),
"allow_show_password": bool(current_stage.password_stage)
and current_stage.password_stage.allow_show_password,
"show_source_labels": current_stage.show_source_labels,
"flow_designation": self.executor.flow.designation,
}
)
@ -215,39 +212,29 @@ class IdentificationStageView(ChallengeStageView[IdentificationStage]):
).name
get_qs = self.request.session.get(SESSION_KEY_GET, self.request.GET)
# Check for related enrollment and recovery flow, add URL to view
if self.current_stage.enrollment_flow:
if current_stage.enrollment_flow:
challenge.initial_data["enroll_url"] = reverse_with_qs(
"authentik_core:if-flow",
query=get_qs,
kwargs={"flow_slug": self.current_stage.enrollment_flow.slug},
kwargs={"flow_slug": current_stage.enrollment_flow.slug},
)
if self.current_stage.recovery_flow:
if current_stage.recovery_flow:
challenge.initial_data["recovery_url"] = reverse_with_qs(
"authentik_core:if-flow",
query=get_qs,
kwargs={"flow_slug": self.current_stage.recovery_flow.slug},
kwargs={"flow_slug": current_stage.recovery_flow.slug},
)
if self.current_stage.passwordless_flow:
if current_stage.passwordless_flow:
challenge.initial_data["passwordless_url"] = reverse_with_qs(
"authentik_core:if-flow",
query=get_qs,
kwargs={"flow_slug": self.current_stage.passwordless_flow.slug},
kwargs={"flow_slug": current_stage.passwordless_flow.slug},
)
if self.current_stage.password_stage:
password = PasswordStageView(self.executor, self.current_stage.captcha_stage)
password_challenge = password.get_challenge()
password_challenge.is_valid()
challenge.initial_data["password_stage"] = password_challenge.data
if self.current_stage.captcha_stage:
captcha = CaptchaStageView(self.executor, self.current_stage.captcha_stage)
captcha_challenge = captcha.get_challenge()
captcha_challenge.is_valid()
challenge.initial_data["captcha_stage"] = captcha_challenge.data
# Check all enabled source, add them if they have a UI Login button.
ui_sources = []
sources: list[Source] = (
self.current_stage.sources.filter(enabled=True).order_by("name").select_subclasses()
current_stage.sources.filter(enabled=True).order_by("name").select_subclasses()
)
for source in sources:
ui_login_button = source.ui_login_button(self.request)
@ -262,7 +249,8 @@ class IdentificationStageView(ChallengeStageView[IdentificationStage]):
def challenge_valid(self, response: IdentificationChallengeResponse) -> HttpResponse:
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = response.pre_user
if not self.current_stage.show_matched_user:
current_stage: IdentificationStage = self.executor.current_stage
if not current_stage.show_matched_user:
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = (
response.validated_data.get("uid_field")
)

View File

@ -17,7 +17,7 @@ INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
class InvitationStageView(StageView[InvitationStage]):
class InvitationStageView(StageView):
"""Finalise Authentication flow by logging the user in"""
def get_token(self) -> str | None:
@ -52,10 +52,11 @@ class InvitationStageView(StageView[InvitationStage]):
def dispatch(self, request: HttpRequest) -> HttpResponse:
"""Apply data to the current flow based on a URL"""
stage: InvitationStage = self.executor.current_stage
invite = self.get_invite()
if not invite:
if self.current_stage.continue_flow_without_invitation:
if stage.continue_flow_without_invitation:
return self.executor.stage_ok()
return self.executor.stage_invalid(_("Invalid invite/invite not found"))

View File

@ -130,7 +130,7 @@ class PasswordChallengeResponse(ChallengeResponse):
return password
class PasswordStageView(ChallengeStageView[PasswordStage]):
class PasswordStageView(ChallengeStageView):
"""Authentication stage which authenticates against django's AuthBackend"""
response_class = PasswordChallengeResponse
@ -138,7 +138,7 @@ class PasswordStageView(ChallengeStageView[PasswordStage]):
def get_challenge(self) -> Challenge:
challenge = PasswordChallenge(
data={
"allow_show_password": self.current_stage.allow_show_password,
"allow_show_password": self.executor.current_stage.allow_show_password,
}
)
recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY)
@ -154,9 +154,10 @@ class PasswordStageView(ChallengeStageView[PasswordStage]):
if SESSION_KEY_INVALID_TRIES not in self.request.session:
self.request.session[SESSION_KEY_INVALID_TRIES] = 0
self.request.session[SESSION_KEY_INVALID_TRIES] += 1
current_stage: PasswordStage = self.executor.current_stage
if (
self.request.session[SESSION_KEY_INVALID_TRIES]
>= self.current_stage.failed_attempts_before_cancel
>= current_stage.failed_attempts_before_cancel
):
self.logger.debug("User has exceeded maximum tries")
del self.request.session[SESSION_KEY_INVALID_TRIES]

View File

@ -222,7 +222,7 @@ class PromptStageView(ChallengeStageView):
return serializers
def get_challenge(self, *args, **kwargs) -> Challenge:
fields: list[Prompt] = list(self.current_stage.fields.all().order_by("order"))
fields: list[Prompt] = list(self.executor.current_stage.fields.all().order_by("order"))
context_prompt = self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {})
serializers = self.get_prompt_challenge_fields(fields, context_prompt)
challenge = PromptChallenge(
@ -239,7 +239,7 @@ class PromptStageView(ChallengeStageView):
instance=None,
data=data,
request=self.request,
stage_instance=self.current_stage,
stage_instance=self.executor.current_stage,
stage=self,
plan=self.executor.plan,
user=self.get_pending_user(),

View File

@ -7,10 +7,9 @@ from django.utils.translation import gettext as _
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.stages.user_delete.models import UserDeleteStage
class UserDeleteStageView(StageView[UserDeleteStage]):
class UserDeleteStageView(StageView):
"""Finalise unenrollment flow by deleting the user object."""
def dispatch(self, request: HttpRequest) -> HttpResponse:

View File

@ -39,7 +39,7 @@ class UserLoginChallengeResponse(ChallengeResponse):
remember_me = BooleanField(required=True)
class UserLoginStageView(ChallengeStageView[UserLoginStage]):
class UserLoginStageView(ChallengeStageView):
"""Finalise Authentication flow by logging the user in"""
response_class = UserLoginChallengeResponse
@ -49,7 +49,8 @@ class UserLoginStageView(ChallengeStageView[UserLoginStage]):
def dispatch(self, request: HttpRequest) -> HttpResponse:
"""Check for remember_me, and do login"""
if timedelta_from_string(self.current_stage.remember_me_offset).total_seconds() > 0:
stage: UserLoginStage = self.executor.current_stage
if timedelta_from_string(stage.remember_me_offset).total_seconds() > 0:
return super().dispatch(request)
return self.do_login(request)
@ -58,9 +59,9 @@ class UserLoginStageView(ChallengeStageView[UserLoginStage]):
def set_session_duration(self, remember: bool) -> timedelta:
"""Update the sessions' expiry"""
delta = timedelta_from_string(self.current_stage.session_duration)
delta = timedelta_from_string(self.executor.current_stage.session_duration)
if remember:
offset = timedelta_from_string(self.current_stage.remember_me_offset)
offset = timedelta_from_string(self.executor.current_stage.remember_me_offset)
delta = delta + offset
if delta.total_seconds() == 0:
self.request.session.set_expiry(0)
@ -70,9 +71,11 @@ class UserLoginStageView(ChallengeStageView[UserLoginStage]):
def set_session_ip(self):
"""Set the sessions' last IP and session bindings"""
stage: UserLoginStage = self.executor.current_stage
self.request.session[SESSION_KEY_LAST_IP] = ClientIPMiddleware.get_client_ip(self.request)
self.request.session[SESSION_KEY_BINDING_NET] = self.current_stage.network_binding
self.request.session[SESSION_KEY_BINDING_GEO] = self.current_stage.geoip_binding
self.request.session[SESSION_KEY_BINDING_NET] = stage.network_binding
self.request.session[SESSION_KEY_BINDING_GEO] = stage.geoip_binding
def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse:
"""Attach the currently pending user to the current session"""
@ -108,7 +111,7 @@ class UserLoginStageView(ChallengeStageView[UserLoginStage]):
# as sources show their own success messages
if not self.executor.plan.context.get(PLAN_CONTEXT_SOURCE, None):
messages.success(self.request, _("Successfully logged in!"))
if self.current_stage.terminate_other_sessions:
if self.executor.current_stage.terminate_other_sessions:
AuthenticatedSession.objects.filter(
user=user,
).exclude(session_key=self.request.session.session_key).delete()

View File

@ -4,10 +4,9 @@ from django.contrib.auth import logout
from django.http import HttpRequest, HttpResponse
from authentik.flows.stage import StageView
from authentik.stages.user_logout.models import UserLogoutStage
class UserLogoutStageView(StageView[UserLogoutStage]):
class UserLogoutStageView(StageView):
"""Finalise Authentication flow by logging the user in"""
def dispatch(self, request: HttpRequest) -> HttpResponse:

View File

@ -55,7 +55,7 @@ class UserWriteStageView(StageView):
"""Ensure a user exists"""
user_created = False
path = self.executor.plan.context.get(
PLAN_CONTEXT_USER_PATH, self.current_stage.user_path_template
PLAN_CONTEXT_USER_PATH, self.executor.current_stage.user_path_template
)
if path == "":
path = User.default_path()
@ -64,11 +64,11 @@ class UserWriteStageView(StageView):
user_type = UserTypes(
self.executor.plan.context.get(
PLAN_CONTEXT_USER_TYPE,
self.current_stage.user_type,
self.executor.current_stage.user_type,
)
)
except ValueError:
user_type = self.current_stage.user_type
user_type = self.executor.current_stage.user_type
if user_type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
user_type = UserTypes.SERVICE_ACCOUNT
@ -76,12 +76,12 @@ class UserWriteStageView(StageView):
self.executor.plan.context.setdefault(PLAN_CONTEXT_PENDING_USER, self.request.user)
if (
PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context
or self.current_stage.user_creation_mode == UserCreationMode.ALWAYS_CREATE
or self.executor.current_stage.user_creation_mode == UserCreationMode.ALWAYS_CREATE
):
if self.current_stage.user_creation_mode == UserCreationMode.NEVER_CREATE:
if self.executor.current_stage.user_creation_mode == UserCreationMode.NEVER_CREATE:
return None, False
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
is_active=not self.current_stage.create_users_as_inactive,
is_active=not self.executor.current_stage.create_users_as_inactive,
path=path,
type=user_type,
)
@ -180,8 +180,8 @@ class UserWriteStageView(StageView):
try:
with transaction.atomic():
user.save()
if self.current_stage.create_users_group:
user.ak_groups.add(self.current_stage.create_users_group)
if self.executor.current_stage.create_users_group:
user.ak_groups.add(self.executor.current_stage.create_users_group)
if PLAN_CONTEXT_GROUPS in self.executor.plan.context:
user.ak_groups.add(*self.executor.plan.context[PLAN_CONTEXT_GROUPS])
except (IntegrityError, ValueError, TypeError, InternalError) as exc:

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2024.6.4 Blueprint schema",
"title": "authentik 2024.8.0 Blueprint schema",
"required": [
"version",
"entries"
@ -10091,11 +10091,6 @@
"title": "Password stage",
"description": "When set, shows a password field, instead of showing the password field as separate step."
},
"captcha_stage": {
"type": "integer",
"title": "Captcha stage",
"description": "When set, the captcha element is shown on the identification stage."
},
"case_insensitive_matching": {
"type": "boolean",
"title": "Case insensitive matching",

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.4}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
restart: unless-stopped
command: server
environment:
@ -52,7 +52,7 @@ services:
- postgresql
- redis
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.4}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
restart: unless-stopped
command: worker
environment:

6
go.mod
View File

@ -18,18 +18,18 @@ require (
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.4.0
github.com/gorilla/websocket v1.5.3
github.com/jellydator/ttlcache/v3 v3.2.0
github.com/jellydator/ttlcache/v3 v3.2.1
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.20.1
github.com/prometheus/client_golang v1.20.2
github.com/redis/go-redis/v9 v9.6.1
github.com/sethvargo/go-envconfig v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024063.13
goauthentik.io/api/v3 v3.2024064.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.22.0
golang.org/x/sync v0.8.0

16
go.sum
View File

@ -200,8 +200,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE=
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jellydator/ttlcache/v3 v3.2.1 h1:eS8ljnYY7BllYGkXw/TfczWZrXUu/CH7SIkC6ugn9Js=
github.com/jellydator/ttlcache/v3 v3.2.1/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -239,8 +239,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8=
github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
@ -297,10 +297,10 @@ go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucg
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024063.13 h1:zWFlrr+8NOaQOCPSRV1FhbDJ58+BPa9BqjNvl4T//s8=
goauthentik.io/api/v3 v3.2024063.13/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2024064.1 h1:vxquklgDGD+nGFhWRAsQ7ezQKg17MRq6bzEk25fbsb4=
goauthentik.io/api/v3 v3.2024064.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2024.6.4"
const VERSION = "2024.8.0"

View File

@ -1,5 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2024.6.4",
"version": "2024.8.0",
"private": true
}

8
poetry.lock generated
View File

@ -1312,17 +1312,17 @@ django = ">=3"
[[package]]
name = "django-pglock"
version = "1.5.1"
version = "1.6.0"
description = "Postgres locking routines and lock table access."
optional = false
python-versions = "<4,>=3.8.0"
files = [
{file = "django_pglock-1.5.1-py3-none-any.whl", hash = "sha256:d3b977922abbaffd43968714b69cdab7453866adf2b0695fb497491748d7bc67"},
{file = "django_pglock-1.5.1.tar.gz", hash = "sha256:291903d5d877b68558003e1d64d764ebd5590344ba3b7aa1d5127df5947869b1"},
{file = "django_pglock-1.6.0-py3-none-any.whl", hash = "sha256:41c98d0bd3738d11e6eaefcc3e5146028f118a593ac58c13d663b751170f01de"},
{file = "django_pglock-1.6.0.tar.gz", hash = "sha256:724450ecc9886f39af599c477d84ad086545a5373215ef7a670cd25faca25a61"},
]
[package.dependencies]
django = ">=3"
django = ">=4"
django-pgactivity = ">=1.2,<2"
[[package]]

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "authentik"
version = "2024.6.4"
version = "2024.8.0"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2024.6.4
version: 2024.8.0
description: Making authentication simple.
contact:
email: hello@goauthentik.io
@ -40457,10 +40457,11 @@ components:
items:
type: string
nullable: true
password_stage:
$ref: '#/components/schemas/PasswordChallenge'
captcha_stage:
$ref: '#/components/schemas/CaptchaChallenge'
password_fields:
type: boolean
allow_show_password:
type: boolean
default: false
application_pre:
type: string
flow_designation:
@ -40481,6 +40482,7 @@ components:
type: boolean
required:
- flow_designation
- password_fields
- primary_action
- show_source_labels
- user_fields
@ -40498,8 +40500,6 @@ components:
password:
type: string
nullable: true
captcha:
$ref: '#/components/schemas/CaptchaChallengeResponseRequest'
required:
- uid_field
IdentificationStage:
@ -40545,12 +40545,6 @@ components:
nullable: true
description: When set, shows a password field, instead of showing the password
field as separate step.
captcha_stage:
type: string
format: uuid
nullable: true
description: When set, the captcha element is shown on the identification
stage.
case_insensitive_matching:
type: boolean
description: When enabled, user fields are matched regardless of their casing.
@ -40619,12 +40613,6 @@ components:
nullable: true
description: When set, shows a password field, instead of showing the password
field as separate step.
captcha_stage:
type: string
format: uuid
nullable: true
description: When set, the captcha element is shown on the identification
stage.
case_insensitive_matching:
type: boolean
description: When enabled, user fields are matched regardless of their casing.
@ -45757,12 +45745,6 @@ components:
nullable: true
description: When set, shows a password field, instead of showing the password
field as separate step.
captcha_stage:
type: string
format: uuid
nullable: true
description: When set, the captcha element is shown on the identification
stage.
case_insensitive_matching:
type: boolean
description: When enabled, user fields are matched regardless of their casing.

904
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@
"guacamole-common-js": "^1.5.0",
"lit": "^3.2.0",
"md-front-matter": "^1.0.4",
"mermaid": "^10.9.1",
"mermaid": "^11.0.2",
"rapidoc": "^9.3.4",
"showdown": "^2.1.0",
"style-mod": "^4.1.2",
@ -51,7 +51,7 @@
"@babel/preset-typescript": "^7.24.7",
"@changesets/cli": "^2.27.5",
"@custom-elements-manifest/analyzer": "^0.10.2",
"@eslint/js": "^9.9.0",
"@eslint/js": "^9.9.1",
"@genesiscommunitysuccess/custom-elements-lsp": "^5.0.3",
"@hcaptcha/types": "^1.0.4",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
@ -101,10 +101,10 @@
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.1.11",
"storybook-addon-mock": "^5.0.0",
"syncpack": "^12.3.3",
"syncpack": "^13.0.0",
"ts-lit-plugin": "^2.0.2",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"tslib": "^2.7.0",
"turnstile-types": "^1.2.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.2.0",

View File

@ -14,7 +14,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.3.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.14",
"@swc/core": "^1.7.18",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jquery": "^3.5.30",
"lockfile-lint": "^4.14.0",
@ -25,7 +25,7 @@
},
"license": "MIT",
"optionalDependencies": {
"@swc/core": "^1.7.14",
"@swc/core": "^1.7.18",
"@swc/core-darwin-arm64": "^1.6.13",
"@swc/core-darwin-x64": "^1.6.13",
"@swc/core-linux-arm-gnueabihf": "^1.6.13",

View File

@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@goauthentik/api": "^2024.6.3-1724337552",
"@goauthentik/api": "^2024.6.3-1724414734",
"base64-js": "^1.5.1",
"bootstrap": "^4.6.1",
"formdata-polyfill": "^4.0.10",
@ -21,16 +21,16 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.3.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.14",
"@swc/core": "^1.7.18",
"@types/jquery": "^3.5.30",
"rollup": "^4.21.0",
"rollup-plugin-copy": "^3.5.0"
}
},
"node_modules/@goauthentik/api": {
"version": "2024.6.3-1724337552",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.6.3-1724337552.tgz",
"integrity": "sha512-siu5qJqUt13iUPsLI0RfieVkDU8IMhuP2i5C/RRqY6oek0z+srSom9UTBAh6n6a2pTTNQO3clE2zxvAIJPahVg=="
"version": "2024.6.3-1724414734",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.6.3-1724414734.tgz",
"integrity": "sha512-2fLKwOh2Znc/unD8Q2U4G0g5QFM4jVqC95e5VRWWVnzp3xB7JWfEDBcRdwyv5PxCdmjBUkvbiul0kiuRwqBf4w=="
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
@ -491,9 +491,9 @@
}
},
"node_modules/@swc/core": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.14.tgz",
"integrity": "sha512-9aeXeifnyuvc2pcuuhPQgVUwdpGEzZ+9nJu0W8/hNl/aESFsJGR5i9uQJRGu0atoNr01gK092fvmqMmQAPcKow==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.18.tgz",
"integrity": "sha512-qL9v5N5S38ijmqiQRvCFUUx2vmxWT/JJ2rswElnyaHkOHuVoAFhBB90Ywj4RKjh3R0zOjhEcemENTyF3q3G6WQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@ -508,16 +508,16 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.7.14",
"@swc/core-darwin-x64": "1.7.14",
"@swc/core-linux-arm-gnueabihf": "1.7.14",
"@swc/core-linux-arm64-gnu": "1.7.14",
"@swc/core-linux-arm64-musl": "1.7.14",
"@swc/core-linux-x64-gnu": "1.7.14",
"@swc/core-linux-x64-musl": "1.7.14",
"@swc/core-win32-arm64-msvc": "1.7.14",
"@swc/core-win32-ia32-msvc": "1.7.14",
"@swc/core-win32-x64-msvc": "1.7.14"
"@swc/core-darwin-arm64": "1.7.18",
"@swc/core-darwin-x64": "1.7.18",
"@swc/core-linux-arm-gnueabihf": "1.7.18",
"@swc/core-linux-arm64-gnu": "1.7.18",
"@swc/core-linux-arm64-musl": "1.7.18",
"@swc/core-linux-x64-gnu": "1.7.18",
"@swc/core-linux-x64-musl": "1.7.18",
"@swc/core-win32-arm64-msvc": "1.7.18",
"@swc/core-win32-ia32-msvc": "1.7.18",
"@swc/core-win32-x64-msvc": "1.7.18"
},
"peerDependencies": {
"@swc/helpers": "*"
@ -529,9 +529,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.14.tgz",
"integrity": "sha512-V0OUXjOH+hdGxDYG8NkQzy25mKOpcNKFpqtZEzLe5V/CpLJPnpg1+pMz70m14s9ZFda9OxsjlvPbg1FLUwhgIQ==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.18.tgz",
"integrity": "sha512-MwLc5U+VGPMZm8MjlFBjEB2wyT1EK0NNJ3tn+ps9fmxdFP+PL8EpMiY1O1F2t1ydy2OzBtZz81sycjM9RieFBg==",
"cpu": [
"arm64"
],
@ -545,9 +545,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.14.tgz",
"integrity": "sha512-9iFvUnxG6FC3An5ogp5jbBfQuUmTTwy8KMB+ZddUoPB3NR1eV+Y9vOh/tfWcenSJbgOKDLgYC5D/b1mHAprsrQ==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.18.tgz",
"integrity": "sha512-IkukOQUw7/14VkHp446OkYGCZEHqZg9pTmTdBawlUyz2JwZMSn2VodCl7aFSdGCsU4Cwni8zKA8CCgkCCAELhw==",
"cpu": [
"x64"
],
@ -561,9 +561,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.14.tgz",
"integrity": "sha512-zGJsef9qPivKSH8Vv4F/HiBXBTHZ5Hs3ZjVGo/UIdWPJF8fTL9OVADiRrl34Q7zOZEtGXRwEKLUW1SCQcbDvZA==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.18.tgz",
"integrity": "sha512-ATnb6jJaBeXCqrTUawWdoOy7eP9SCI7UMcfXlYIMxX4otKKspLPAEuGA5RaNxlCcj9ObyO0J3YGbtZ6hhD2pjg==",
"cpu": [
"arm"
],
@ -577,9 +577,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.14.tgz",
"integrity": "sha512-AxV3MPsoI7i4B8FXOew3dx3N8y00YoJYvIPfxelw07RegeCEH3aHp2U2DtgbP/NV1ugZMx0TL2Z2DEvocmA51g==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.18.tgz",
"integrity": "sha512-poHtH7zL7lEp9K2inY90lGHJABWxURAOgWNeZqrcR5+jwIe7q5KBisysH09Zf/JNF9+6iNns+U0xgWTNJzBuGA==",
"cpu": [
"arm64"
],
@ -593,9 +593,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.14.tgz",
"integrity": "sha512-JDLdNjUj3zPehd4+DrQD8Ltb3B5lD8D05IwePyDWw+uR/YPc7w/TX1FUVci5h3giJnlMCJRvi1IQYV7K1n7KtQ==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.18.tgz",
"integrity": "sha512-qnNI1WmcOV7Wz1ZDyK6WrOlzLvJ01rnni8ec950mMHWkLRMP53QvCvhF3S+7gFplWBwWJTOOPPUqJp/PlSxWyQ==",
"cpu": [
"arm64"
],
@ -609,9 +609,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.14.tgz",
"integrity": "sha512-Siy5OvPCLLWmMdx4msnEs8HvEVUEigSn0+3pbLjv78iwzXd0qSBNHUPZyC1xeurVaUbpNDxZTpPRIwpqNE2+Og==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.18.tgz",
"integrity": "sha512-x9SCqCLzwtlqtD5At3I1a7Gco+EuXnzrJGoucmkpeQohshHuwa+cskqsXO6u1Dz0jXJEuHbBZB9va1wYYfjgFg==",
"cpu": [
"x64"
],
@ -625,9 +625,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.14.tgz",
"integrity": "sha512-FtEGm9mwtRYQNK43WMtUIadxHs/ja2rnDurB99os0ZoFTGG2IHuht2zD97W0wB8JbqEabT1XwSG9Y5wmN+ciEQ==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.18.tgz",
"integrity": "sha512-qtj8iOpMMgKjzxTv+islmEY0JBsbd93nka0gzcTTmGZxKtL5jSUsYQvkxwNPZr5M9NU1fgaR3n1vE6lFmtY0IQ==",
"cpu": [
"x64"
],
@ -641,9 +641,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.14.tgz",
"integrity": "sha512-Jp8KDlfq7Ntt2/BXr0y344cYgB1zf0DaLzDZ1ZJR6rYlAzWYSccLYcxHa97VGnsYhhPspMpmCvHid97oe2hl4A==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.18.tgz",
"integrity": "sha512-ltX/Ol9+Qu4SXmISCeuwVgAjSa8nzHTymknpozzVMgjXUoZMoz6lcynfKL1nCh5XLgqh0XNHUKLti5YFF8LrrA==",
"cpu": [
"arm64"
],
@ -657,9 +657,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.14.tgz",
"integrity": "sha512-I+cFsXF0OU0J9J4zdWiQKKLURO5dvCujH9Jr8N0cErdy54l9d4gfIxdctfTF+7FyXtWKLTCkp+oby9BQhkFGWA==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.18.tgz",
"integrity": "sha512-RgTcFP3wgyxnQbTCJrlgBJmgpeTXo8t807GU9GxApAXfpLZJ3swJ2GgFUmIJVdLWyffSHF5BEkF3FmF6mtH5AQ==",
"cpu": [
"ia32"
],
@ -673,9 +673,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.14.tgz",
"integrity": "sha512-NNrprQCK6d28mG436jVo2TD+vACHseUECacEBGZ9Ef0qfOIWS1XIt2MisQKG0Oea2VvLFl6tF/V4Lnx/H0Sn3Q==",
"version": "1.7.18",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.18.tgz",
"integrity": "sha512-XbZ0wAgzR757+DhQcnv60Y/bK9yuWPhDNRQVFFQVRsowvK3+c6EblyfUSytIidpXgyYFzlprq/9A9ZlO/wvDWw==",
"cpu": [
"x64"
],

View File

@ -4,7 +4,7 @@
"private": true,
"license": "MIT",
"dependencies": {
"@goauthentik/api": "^2024.6.3-1724337552",
"@goauthentik/api": "^2024.6.3-1724414734",
"base64-js": "^1.5.1",
"bootstrap": "^4.6.1",
"formdata-polyfill": "^4.0.10",
@ -20,7 +20,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.3.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.14",
"@swc/core": "^1.7.18",
"@types/jquery": "^3.5.30",
"rollup": "^4.21.0",
"rollup-plugin-copy": "^3.5.0"

View File

@ -21,7 +21,6 @@ import {
SourcesApi,
Stage,
StagesApi,
StagesCaptchaListRequest,
StagesPasswordListRequest,
UserFieldsEnum,
} from "@goauthentik/api";
@ -47,7 +46,8 @@ async function makeSourcesSelector(instanceSources: string[] | undefined) {
return localSources
? ([pk, _]: DualSelectPair) => localSources.has(pk)
: ([_0, _1, _2, source]: DualSelectPair<Source>) =>
: // Creating a new instance, auto-select built-in source only when no other sources exist
([_0, _1, _2, source]: DualSelectPair<Source>) =>
source !== undefined && source.component === "";
}
@ -76,11 +76,11 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
stageUuid: this.instance.pk || "",
identificationStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesIdentificationCreate({
identificationStageRequest: data,
});
}
return new StagesApi(DEFAULT_CONFIG).stagesIdentificationCreate({
identificationStageRequest: data,
});
}
isUserFieldSelected(field: UserFieldsEnum): boolean {
@ -161,37 +161,6 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Captcha stage")} name="captchaStage">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
const args: StagesCaptchaListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const stages = await new StagesApi(
DEFAULT_CONFIG,
).stagesCaptchaList(args);
return stages.results;
}}
.groupBy=${(items: Stage[]) => {
return groupBy(items, (stage) => stage.verboseNamePlural);
}}
.renderElement=${(stage: Stage): string => {
return stage.name;
}}
.value=${(stage: Stage | undefined): string | undefined => {
return stage?.pk;
}}
.selected=${(stage: Stage): boolean => {
return stage.pk === this.instance?.captchaStage;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">${msg("TODO.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="caseInsensitiveMatching">
<label class="pf-c-switch">
<input
@ -264,12 +233,12 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
?required=${true}
name="sources"
>
<ak-dual-select-provider-dynamic-selected
<ak-dual-select-dynamic-selected
.provider=${sourcesProvider}
.selected=${makeSourcesSelector(this.instance?.sources)}
.selector=${makeSourcesSelector(this.instance?.sources)}
available-label="${msg("Available Stages")}"
selected-label="${msg("Selected Stages")}"
></ak-dual-select-provider-dynamic-selected>
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg(
"Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.",

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2024.6.4";
export const VERSION = "2024.8.0";
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";

View File

@ -41,7 +41,7 @@ export class Diagram extends AKElement {
// The type definition for this says number
// but the example use strings
// and numbers don't work
logLevel: "fatal" as unknown as number,
logLevel: "fatal",
startOnLoad: false,
flowchart: {
curve: "linear",

View File

@ -50,3 +50,9 @@ export class AkDualSelectDynamic extends AkDualSelectProvider {
></ak-dual-select>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-dual-select-dynamic-selected": AkDualSelectDynamic;
}
}

View File

@ -5,6 +5,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -32,7 +33,17 @@ export class AccessDeniedStage extends BaseStage<
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form">
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<ak-empty-state icon="fa-times" header=${msg("Request has been denied.")}>
${this.challenge.errorMessage
? html`

View File

@ -7,6 +7,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -76,7 +77,17 @@ export class AuthenticatorDuoStage extends BaseStage<
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<img
src=${this.challenge.activationBarcode}
alt=${msg("Duo activation QR code")}

View File

@ -41,7 +41,17 @@ export class AuthenticatorSMSStage extends BaseStage<
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<ak-form-element
label="${msg("Phone number")}"
required

View File

@ -6,6 +6,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -65,7 +66,17 @@ export class AuthenticatorStaticStage extends BaseStage<
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<ak-form-element label="" class="pf-c-form__group">
<ul>
${this.challenge.codes.map((token) => {

View File

@ -9,6 +9,7 @@ import "webcomponent-qr-code";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -59,7 +60,17 @@ export class AuthenticatorTOTPStage extends BaseStage<
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<input type="hidden" name="otp_uri" value=${this.challenge.configUrl} />
<ak-form-element>
<div class="qr-container">

View File

@ -10,6 +10,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg, str } from "@lit/localize";
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -132,7 +133,17 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form">
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<ak-empty-state
?loading="${this.registerRunning}"
header=${this.registerRunning

View File

@ -7,7 +7,8 @@ import type { TurnstileObject } from "turnstile-types";
import { msg } from "@lit/localize";
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -44,9 +45,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
@state()
scriptElement?: HTMLScriptElement;
@property({ type: Boolean })
embedded = false;
constructor() {
super();
this.captchaContainer = document.createElement("div");
@ -163,9 +161,6 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
}
render(): TemplateResult {
if (this.embedded) {
return this.renderBody();
}
if (!this.challenge) {
return html`<ak-empty-state loading> </ak-empty-state>`;
}
@ -173,7 +168,18 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
</header>
<div class="pf-c-login__main-body">
${this.renderUserInfo()}
<form class="pf-c-form">
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
${this.renderBody()}
</form>
</div>

View File

@ -5,6 +5,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -108,7 +109,17 @@ export class ConsentStage extends BaseStage<ConsentChallenge, ConsentChallengeRe
});
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
${this.challenge.additionalPermissions.length > 0
? this.renderAdditional()
: this.renderNoPrevious()}

View File

@ -4,7 +4,6 @@ import "@goauthentik/elements/EmptyState";
import "@goauthentik/elements/forms/FormElement";
import "@goauthentik/flow/components/ak-flow-password-input.js";
import { BaseStage } from "@goauthentik/flow/stages/base";
import "@goauthentik/flow/stages/captcha/CaptchaStage";
import { msg, str } from "@lit/localize";
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
@ -124,7 +123,7 @@ export class IdentificationStage extends BaseStage<
this.form.appendChild(username);
}
// Only add the password field when we don't already show a password field
if (!compatMode && !this.challenge.passwordStage) {
if (!compatMode && !this.challenge.passwordFields) {
const password = document.createElement("input");
password.setAttribute("type", "password");
password.setAttribute("name", "password");
@ -261,7 +260,7 @@ export class IdentificationStage extends BaseStage<
required
/>
</ak-form-element>
${this.challenge.passwordStage
${this.challenge.passwordFields
? html`
<ak-flow-input-password
label=${msg("Password")}
@ -269,20 +268,12 @@ export class IdentificationStage extends BaseStage<
required
class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["password"]}
?allow-show-password=${this.challenge.passwordStage.allowShowPassword}
?allow-show-password=${this.challenge.allowShowPassword}
prefill=${PasswordManagerPrefill["password"] ?? ""}
></ak-flow-input-password>
`
: nothing}
${this.renderNonFieldErrors()}
${this.challenge.captchaStage
? html`
<ak-stage-captcha
.challenge=${this.challenge.captchaStage}
embedded
></ak-stage-captcha>
`
: nothing}
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${this.challenge.primaryAction}
@ -293,13 +284,6 @@ export class IdentificationStage extends BaseStage<
: nothing}`;
}
submitForm(
e: Event,
defaults?: IdentificationChallengeResponseRequest | undefined,
): Promise<boolean> {
return super.submitForm(e, defaults);
}
render(): TemplateResult {
if (!this.challenge) {
return html`<ak-empty-state loading> </ak-empty-state>`;

View File

@ -8,6 +8,7 @@ import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -44,7 +45,17 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<input
name="username"
autocomplete="username"

View File

@ -6,6 +6,7 @@ import { BaseStage } from "@goauthentik/flow/stages/base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -35,7 +36,17 @@ export class PasswordStage extends BaseStage<
</header>
<div class="pf-c-login__main-body">
<form class="pf-c-form">
${this.renderUserInfo()}
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${msg("Not you?")}</a
>
</div>
</ak-form-static>
<div class="pf-c-form__group">
<h3 id="header-text" class="pf-c-title pf-m-xl pf-u-mb-xl">
${msg("Stay signed in?")}

View File

@ -6860,6 +6860,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -7125,6 +7125,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -6777,6 +6777,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -9022,6 +9022,36 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -8696,6 +8696,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -8541,6 +8541,36 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -8961,6 +8961,36 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -8924,4 +8924,34 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body></file></xliff>

View File

@ -9025,6 +9025,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -6770,6 +6770,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -5707,6 +5707,36 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -9028,6 +9028,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -6818,6 +6818,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -8657,6 +8657,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="sa65f772cfc5aa67e">
<source>Selected Transports</source>
</trans-unit>
<trans-unit id="sd923f95605fed7b2">
<source>Expired</source>
</trans-unit>
<trans-unit id="s86959994f28077dc">
<source>Expiring soon</source>
</trans-unit>
<trans-unit id="sc60a5fba70bf5a53">
<source>Unlicensed</source>
</trans-unit>
<trans-unit id="s4f9880ce82953741">
<source>Read Only</source>
</trans-unit>
<trans-unit id="s4220dda46d622211">
<source>Valid</source>
</trans-unit>
<trans-unit id="sdc94711a2eb66a45">
<source>Current license status</source>
</trans-unit>
<trans-unit id="se10fb73c1f1039c5">
<source>Overall license status</source>
</trans-unit>
<trans-unit id="sc4804358f202968c">
<source>Internal user usage</source>
</trans-unit>
<trans-unit id="s087d6f07b52b30ec">
<source><x id="0" equiv-text="${internalUserPercentage &lt; Infinity ? internalUserPercentage : &quot;∞&quot;}"/>%</source>
</trans-unit>
<trans-unit id="sae72e1569d34fb02">
<source>External user usage</source>
</trans-unit>
</body>
</file>

View File

@ -36,11 +36,11 @@ To disable existing blueprints, an empty file can be mounted over the existing b
File-based blueprints are automatically removed once they become unavailable, however none of the objects created by those blueprints afre affected by this.
:::info
Please note that, by default, blueprint discovery and evaluation is not guaranteed to follow any specific order.
:::info
Please note that, by default, blueprint discovery and evaluation is not guaranteed to follow any specific order.
If you have dependencies between blueprints, you should use [meta models](/developer-docs/blueprints/v1/meta#authentik_blueprintsmetaapplyblueprint) to make sure that objects are created in the correct order.
:::
If you have dependencies between blueprints, you should use [meta models](./v1/meta#authentik_blueprintsmetaapplyblueprint) to make sure that objects are created in the correct order.
:::
## Storage - OCI

View File

@ -105,11 +105,7 @@ The following events occur when a license expeires and is not renewed within two
### About users and licenses
License usage is calculated based on total user counts and log-in data data that authentik regularly captures. This data is checked against all valid licenses, and the sum total of all users.
- The **_internal user_** count is calculated based on actual users assigned to the organization.
- The **_external user_** count is calculated based on how many external users were active (i.e. logged in) since the start of the current month.
License usage is calculated based on total user counts that authentik regularly captures. This data is checked against all valid licenses, and the sum total of all users. Internal and external users are counted based on the number of active users of the respective type saved in authentik. Service account users are not counted towards the license.
:::info
An **internal** user is typically a team member, such as company employees, who has access to the full Enterprise feature set. An **external** user might be an external consultant, a volunteer in a charitable site, or a B2C customer who logged onto your website to shop. These users don't get access to Enterprise features.

View File

@ -18,7 +18,8 @@ Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=application_client_id&
username=my-service-account&
password=my-token
password=my-token&
scope=profile
```
This will return a JSON response with an `access_token`, which is a signed JWT token. This token can be sent along requests to other hosts, which can then validate the JWT based on the signing key configured in authentik.

File diff suppressed because it is too large Load Diff

View File

@ -341,7 +341,7 @@ def get_as_base64(url):
def get_avatar_from_avatar_url(url):
"""Returns an authentik-avatar-attributes-compatible string from an image url"""
cut_url = f"{url}?size=64"
cut_url = f"{url}"
return AVATAR_STREAM_CONTENT.format(
base64_string=(get_as_base64(cut_url).decode("utf-8"))
)

View File

@ -12322,10 +12322,11 @@
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.5",
"license": "MIT",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {