Compare commits

..

97 Commits

Author SHA1 Message Date
06848be14b fix nak in peap
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:04 +02:00
4bae3bbe60 most of gtc
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:04 +02:00
e33f839d7f add basic testing readme
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:04 +02:00
f5eb827d14 start reworking response modification
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
9045f5ba73 add tests for peap-extensions
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
7b97e92094 hmm
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
3027cdcc4b mschapv2 working
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
67f627a925 mostly working
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
f1101e0c01 mostly parsing eavp extensions
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
fb01a117ad encode extension AVPs
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00
fad18db70b more mschap v2, start peap extension type 33
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
e0c837257c fix decode not called in inner protocol
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
2a567ccc85 peap: fix encode
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
e36373ceab cleanup parsing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
d8a625be03 fix a bunch of stuff ig
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
4d944f7444 eap/tls: trunc data to size we read
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:02 +02:00
c49274042b slightly better decoding
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:01 +02:00
10fc15ffe0 more debug tools
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:01 +02:00
7c996d9d9d start handling inner
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:01 +02:00
5d25f68b71 start inner STM
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:01 +02:00
8da54d5811 more refactor
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:00 +02:00
4571f5e644 working PEAP decode
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:00 +02:00
ee234ea3aa simplify
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:00 +02:00
82c177b7eb try to make this work
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:00 +02:00
1155ccb3e8 support SSLKEYLOGFILE
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:00 +02:00
1575b96262 separate eap logic into protocol
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
19bb77638a folder structure to prepare eap in eap
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
d6cf129eaa attempt peap
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
b6686cff14 refactor v1, start support for more protocols and implement nak
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
8cf8f1e199 keep eap state when refreshing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
50c50c4109 remove panic
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:59 +02:00
51f4a8d83d fix outpost not having perms for cert
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:58 +02:00
3ada3a7e0e make certificate configurable
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:58 +02:00
fa06c9fe4e start tying it into the flow
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:58 +02:00
2a024238fe slightly better logging
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:58 +02:00
91c87b7c3c ok this works kinda
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:58 +02:00
318443f270 hmmm idk
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
ac88784089 maybe?
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
855afa7b9f slight read refactor (seems to fix flaky issues?)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
240abfef41 use tighter retry that cancels and backs off
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
03075f1890 slight refactor
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
5bc0ed6e11 apparently it works now
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
8f4cfc28c7 fix outgoing buffer not cleared when sending unchunked
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:57 +02:00
6d77eaaab7 deduplicate
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
9cee59537c prep ctx
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
fc5c0e2789 generate MPPE key
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
573446689f fix remaning tls data not sent
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
fd4bfe604d more fixup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
06e76a5b37 it's almost working
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:56 +02:00
3c228bf5c3 try to make the finish work
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:55 +02:00
8a80f07db2 this might actually be cooking
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:55 +02:00
ae59a3e576 we're getting somewhere
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:55 +02:00
df21e678d6 fix a bunch more
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:55 +02:00
a71532b3e3 refactor more
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:55 +02:00
d7cb0b3ea1 fixup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:54 +02:00
ba8f137885 keep track of total payload size
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:54 +02:00
958ff66070 fix parsing when lengincluded is not set
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:54 +02:00
ad57c66a32 better log
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:54 +02:00
2bba0ddd74 might actually happen?
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:42:54 +02:00
767c0a8e45 website/docs: enhance customization docs (#15081)
* drafty

* rearrange everything

* fix links

* fixed link

* more links to fix

* tweaks

* not sure

* Optimised images with calibre/image-actions

* Update website/docs/add-secure-apps/providers/rac/index.md

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/_global/global.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/sys-mgmt/brands.md

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/_global/global.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/add-secure-apps/providers/rac/index.md

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/_global/global.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user-admin/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user-admin/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user-admin/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user-admin/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user-admin/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/netlify.toml

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/flow/customization.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* tweak

* separated admin and user again

* so many busted things, now unbusted

* redirect yesterday's redirects

* dewi edits

* tweak to bump build

* revert package.json change

* links, tweaks

* Optimised images with calibre/image-actions

* Update website/docs/customize/interfaces/admin/customization_admin_ui.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/admin/customization_admin_ui.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/flow/customization_flow.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/flow/customization_flow.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/user/customization_user_ui.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/customize/interfaces/admin/customization_admin_ui.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* simplify sidebar

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

* fix redirect indent

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

* rename files

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

* flows -> flow interface

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

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 15:24:56 -05:00
b10c795a26 website: bump the build group across 1 directory with 9 updates (#15332)
Bumps the build group with 9 updates in the /website directory:

| Package | From | To |
| --- | --- | --- |
| [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.4.1` | `1.4.2` |
| [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.4.1` | `1.4.2` |
| [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.4.1` | `1.4.2` |
| [@swc/core-darwin-arm64](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |
| [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |
| [@swc/core-linux-x64-gnu](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |
| [@swc/html-darwin-arm64](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |
| [@swc/html-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |
| [@swc/html-linux-x64-gnu](https://github.com/swc-project/swc) | `1.12.7` | `1.12.9` |



Updates `@rspack/binding-darwin-arm64` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.2/packages/rspack)

Updates `@rspack/binding-linux-arm64-gnu` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.2/packages/rspack)

Updates `@rspack/binding-linux-x64-gnu` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.2/packages/rspack)

Updates `@swc/core-darwin-arm64` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

Updates `@swc/core-linux-arm64-gnu` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

Updates `@swc/core-linux-x64-gnu` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

Updates `@swc/html-darwin-arm64` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

Updates `@swc/html-linux-arm64-gnu` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

Updates `@swc/html-linux-x64-gnu` from 1.12.7 to 1.12.9
- [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.12.7...v1.12.9)

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-darwin-arm64"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-arm64-gnu"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-x64-gnu"
  dependency-version: 1.12.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 20:16:11 +02:00
8088e08fd9 website/docs: re-add gtag (#15334)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 19:32:39 +02:00
eab6e288d7 core: bump lxml from 5.4.0 to 6.0.0 (#15281)
Bumps [lxml](https://github.com/lxml/lxml) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-5.4.0...lxml-6.0.0)

---
updated-dependencies:
- dependency-name: lxml
  dependency-version: 6.0.0
  dependency-type: direct:production
  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>
2025-07-01 17:19:01 +02:00
91c2863358 website: bump @types/node from 24.0.7 to 24.0.8 in /website (#15328)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.7 to 24.0.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.8
  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>
2025-07-01 17:01:24 +02:00
1638e95bc7 website: bump the build group in /website with 3 updates (#15279)
Bumps the build group in /website with 3 updates: [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack), [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) and [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack).


Updates `@rspack/binding-darwin-arm64` from 1.3.15 to 1.4.0
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.0/packages/rspack)

Updates `@rspack/binding-linux-arm64-gnu` from 1.3.15 to 1.4.0
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.0/packages/rspack)

Updates `@rspack/binding-linux-x64-gnu` from 1.3.15 to 1.4.0
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.0/packages/rspack)

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 17:00:46 +02:00
8f75131541 website: bump the eslint group in /website with 3 updates (#15329)
Bumps the eslint group in /website with 3 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@typescript-eslint/eslint-plugin` from 8.35.0 to 8.35.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.35.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.35.0 to 8.35.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.35.1/packages/parser)

Updates `typescript-eslint` from 8.35.0 to 8.35.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.35.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.35.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.35.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.35.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 17:00:00 +02:00
c85471575a stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#15327)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-07-01 10:40:19 +02:00
5d00dc7e9e enterprise/search: fix search fallback for non QL queries (#15325)
* enterprise/search: fix search fallback for non QL queries

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

* fix fixed tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 00:55:23 +02:00
6982e7d1c9 web/elements: fix table search not resetting page when query changes (#15324)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 00:55:09 +02:00
c7fe987c5a core: fix missing serializer on AuthenticatedSession (#15323)
fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-30 21:30:34 +02:00
e48739c8a0 stages/email: remove superflous <td> from account_confirmation template (#15297)
🐛 FIX: stages/email: remove superflous <td> from account_confirmation template

Signed-off-by: Jonas Sulzer <jonas@violoncello.ch>
2025-06-30 11:02:52 +00:00
b2ee585c43 website: bump @types/node from 24.0.4 to 24.0.7 in /website (#15307)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.4 to 24.0.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.7
  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>
2025-06-30 12:37:30 +02:00
97e8ea8e76 website: bump prettier-plugin-packagejson from 2.5.16 to 2.5.17 in /website (#15308)
website: bump prettier-plugin-packagejson in /website

Bumps [prettier-plugin-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson) from 2.5.16 to 2.5.17.
- [Release notes](https://github.com/matzkoh/prettier-plugin-packagejson/releases)
- [Commits](https://github.com/matzkoh/prettier-plugin-packagejson/compare/v2.5.16...v2.5.17)

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.17
  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>
2025-06-30 12:37:11 +02:00
1f1e0c9db1 website: bump the eslint group in /website with 2 updates (#15309)
Bumps the eslint group in /website with 2 updates: [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) and [eslint](https://github.com/eslint/eslint).


Updates `@eslint/js` from 9.29.0 to 9.30.0
- [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.30.0/packages/js)

Updates `eslint` from 9.29.0 to 9.30.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.29.0...v9.30.0)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 12:37:01 +02:00
ca47a803fe core: bump uvicorn[standard] from 0.34.3 to 0.35.0 (#15312)
Bumps [uvicorn[standard]](https://github.com/encode/uvicorn) from 0.34.3 to 0.35.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/docs/release-notes.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.34.3...0.35.0)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 12:36:45 +02:00
c606eb53b0 core: bump goauthentik.io/api/v3 from 3.2025062.6 to 3.2025063.1 (#15306)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 09:32:40 +00:00
62357133b0 core: bump astral-sh/uv from 0.7.15 to 0.7.17 (#15311)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 10:03:54 +02:00
99d2d91257 core: bump sentry-sdk from 2.31.0 to 2.32.0 (#15314)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 10:02:43 +02:00
69d9363fce core: bump axllent/mailpit from v1.26.2 to v1.27.0 in /tests/e2e (#15315)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 10:02:28 +02:00
cfc7f6b993 core, web: update translations (#15298)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-06-30 03:17:37 +02:00
bebbbe9b90 website/integrations: add omada controller doc (#14523)
* Adds omada controller doc and updated integration sidebar

* Update verification section

* WIP

* WIP

* Updated encoding section

* Finished document

* Applied suggestions

* Applied suggestions

* Entity ID changed to issuer

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/integrations/services/omada-controller/index.mdx

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* And then

* Remove errant :::

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-06-28 15:50:22 +00:00
188d3c69c1 Change issuer from zulip to authentik in zulip integration documentation (#15296)
I tried to follow this documentation. My zulip instance (running docker-zulip 10.3-0)  did not recognize the IdP when a user returned from authentik to zulip, until I changed the issuer in the authentik settings from `zulip.company` to `authentik.company`.

Signed-off-by: CSDUMMI <31551856+CSDUMMI@users.noreply.github.com>
2025-06-28 13:25:54 +01:00
877f312145 website/integrations: bookstack: fix redirect URI (#15295)
Signed-off-by: Dominic R <dominic@sdko.org>
2025-06-28 11:13:19 +01:00
f471a98bc7 website/integrations: fix typo in actual budget (#15293)
docs: Update index.mdx

Signed-off-by: Bryan J. <132493975+chkpwd@users.noreply.github.com>
2025-06-27 12:52:42 -05:00
e874cfc21d website: bump prettier from 3.6.1 to 3.6.2 in /website (#15280)
Bumps [prettier](https://github.com/prettier/prettier) from 3.6.1 to 3.6.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.6.1...3.6.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.6.2
  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>
2025-06-27 17:04:52 +02:00
ec7bdf74aa core, web: update translations (#15278)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-06-27 14:56:51 +00:00
e87bc94b95 release: backport 2025.6.3 (#15292)
release: 2025.6.3
2025-06-27 16:21:18 +02:00
a3865abaa9 website: changelog for security releases (#15291)
* website: changelog for security releases

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

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-27 15:42:02 +02:00
7100d3c674 security: fix CVE-2025-52553 (#15289)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-27 15:26:39 +02:00
c0c2d2ad3c website/docs: updated security release procedure (#15288)
* ci: skip translate compile

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

* ci: allow skipping build container for website

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

* update docs

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

* fix gha perms?

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-27 14:18:29 +02:00
dc287989db translate: Updates for file web/xliff/en.xlf in zh-Hans (#15285)
Translate web/xliff/en.xlf in zh-Hans

100% translated source file: 'web/xliff/en.xlf'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 14:02:01 +02:00
03204f6943 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#15284)
Translate django.po in zh-Hans

100% translated source file: 'django.po'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 14:01:48 +02:00
fcd369e466 translate: Updates for file web/xliff/en.xlf in zh_CN (#15283)
Translate web/xliff/en.xlf in zh_CN

100% translated source file: 'web/xliff/en.xlf'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 14:01:38 +02:00
cb79407bc1 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#15282)
Translate locale/en/LC_MESSAGES/django.po in zh_CN

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 14:01:16 +02:00
04a88daf34 translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#15273)
Translate locale/en/LC_MESSAGES/django.po in it

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 13:59:54 +02:00
c6a49da5c3 translate: Updates for file web/xliff/en.xlf in it (#15272)
Translate web/xliff/en.xlf in it

100% translated source file: 'web/xliff/en.xlf'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-06-27 13:59:35 +02:00
bfeeecf3fa ci: more adjustable mirror options (#15287)
* custom mirror which doesn't get rid of other branches

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

* add workflow for manual semi-release

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

* make ci work on internal

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-27 13:48:20 +02:00
745 changed files with 7968 additions and 7642 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2025.6.2
current_version = 2025.6.3
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

@ -38,6 +38,8 @@ jobs:
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3.6.0

View File

@ -9,6 +9,7 @@ on:
jobs:
test-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false

View File

@ -247,11 +247,13 @@ jobs:
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
needs: ci-core-mark
uses: ./.github/workflows/_reusable-docker-build.yaml
secrets: inherit
with:
image_name: ghcr.io/goauthentik/dev-server
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
release: false
pr-comment:
needs:

View File

@ -59,6 +59,7 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
timeout-minutes: 120
needs:
- ci-outpost-mark

View File

@ -63,6 +63,7 @@ jobs:
working-directory: website/
run: npm run ${{ matrix.job }}
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
@ -122,3 +123,4 @@ jobs:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: ${{ github.repository == 'goauthentik/authentik-internal' && 'build-container' || '[]' }}

View File

@ -0,0 +1,21 @@
name: "authentik-repo-mirror-cleanup"
on:
workflow_dispatch:
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb
with:
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force --prune
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@ -11,11 +11,10 @@ jobs:
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: pixta-dev/repository-mirroring-action@v1
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb
with:
target_repo_url:
git@github.com:goauthentik/authentik-internal.git
ssh_private_key:
${{ secrets.GH_MIRROR_KEY }}
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@ -16,6 +16,7 @@ env:
jobs:
compile:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.7.15 AS uv
FROM ghcr.io/astral-sh/uv:0.7.17 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2025.6.2"
__version__ = "2025.6.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -5,7 +5,6 @@ from collections.abc import Callable
from django.apps import apps
from django.test import TestCase
from authentik.blueprints.v1.importer import is_model_allowed
from authentik.lib.models import SerializerModel
from authentik.providers.oauth2.models import RefreshToken
@ -22,10 +21,13 @@ def serializer_tester_factory(test_model: type[SerializerModel]) -> Callable:
return
model_class = test_model()
self.assertTrue(isinstance(model_class, SerializerModel))
# Models that have subclasses don't have to have a serializer
if len(test_model.__subclasses__()) > 0:
return
self.assertIsNotNone(model_class.serializer)
if model_class.serializer.Meta().model == RefreshToken:
return
self.assertEqual(model_class.serializer.Meta().model, test_model)
self.assertTrue(issubclass(test_model, model_class.serializer.Meta().model))
return tester
@ -34,6 +36,6 @@ for app in apps.get_app_configs():
if not app.label.startswith("authentik"):
continue
for model in app.get_models():
if not is_model_allowed(model):
if not issubclass(model, SerializerModel):
continue
setattr(TestModels, f"test_{app.label}_{model.__name__}", serializer_tester_factory(model))

View File

@ -1082,6 +1082,12 @@ class AuthenticatedSession(SerializerModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.core.api.authenticated_sessions import AuthenticatedSessionSerializer
return AuthenticatedSessionSerializer
class Meta:
verbose_name = _("Authenticated Session")
verbose_name_plural = _("Authenticated Sessions")

View File

@ -6,7 +6,7 @@ from djangoql.ast import Name
from djangoql.exceptions import DjangoQLError
from djangoql.queryset import apply_search
from djangoql.schema import DjangoQLSchema
from rest_framework.filters import SearchFilter
from rest_framework.filters import BaseFilterBackend, SearchFilter
from rest_framework.request import Request
from structlog.stdlib import get_logger
@ -39,19 +39,21 @@ class BaseSchema(DjangoQLSchema):
return super().resolve_name(name)
class QLSearch(SearchFilter):
class QLSearch(BaseFilterBackend):
"""rest_framework search filter which uses DjangoQL"""
def __init__(self):
super().__init__()
self._fallback = SearchFilter()
@property
def enabled(self):
return apps.get_app_config("authentik_enterprise").enabled()
def get_search_terms(self, request) -> str:
"""
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
params = request.query_params.get(self.search_param, "")
def get_search_terms(self, request: Request) -> str:
"""Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited."""
params = request.query_params.get("search", "")
params = params.replace("\x00", "") # strip null characters
return params
@ -70,9 +72,9 @@ class QLSearch(SearchFilter):
search_query = self.get_search_terms(request)
schema = self.get_schema(request, view)
if len(search_query) == 0 or not self.enabled:
return super().filter_queryset(request, queryset, view)
return self._fallback.filter_queryset(request, queryset, view)
try:
return apply_search(queryset, search_query, schema=schema)
except DjangoQLError as exc:
LOGGER.debug("Failed to parse search expression", exc=exc)
return super().filter_queryset(request, queryset, view)
return self._fallback.filter_queryset(request, queryset, view)

View File

@ -57,7 +57,7 @@ class QLTest(APITestCase):
)
self.assertEqual(res.status_code, 200)
content = loads(res.content)
self.assertGreaterEqual(content["pagination"]["count"], 1)
self.assertEqual(content["pagination"]["count"], 1)
self.assertEqual(content["results"][0]["username"], self.user.username)
def test_search_json(self):

View File

@ -66,7 +66,10 @@ class RACClientConsumer(AsyncWebsocketConsumer):
def init_outpost_connection(self):
"""Initialize guac connection settings"""
self.token = (
ConnectionToken.filter_not_expired(token=self.scope["url_route"]["kwargs"]["token"])
ConnectionToken.filter_not_expired(
token=self.scope["url_route"]["kwargs"]["token"],
session__session__session_key=self.scope["session"].session_key,
)
.select_related("endpoint", "provider", "session", "session__user")
.first()
)

View File

@ -87,3 +87,22 @@ class TestRACViews(APITestCase):
)
body = loads(flow_response.content)
self.assertEqual(body["component"], "ak-stage-access-denied")
def test_different_session(self):
"""Test request"""
self.client.force_login(self.user)
response = self.client.get(
reverse(
"authentik_providers_rac:start",
kwargs={"app": self.app.slug, "endpoint": str(self.endpoint.pk)},
)
)
self.assertEqual(response.status_code, 302)
flow_response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
body = loads(flow_response.content)
next_url = body["to"]
self.client.logout()
final_response = self.client.get(next_url)
self.assertEqual(final_response.url, reverse("authentik_core:if-user"))

View File

@ -68,7 +68,10 @@ class RACInterface(InterfaceView):
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
# Early sanity check to ensure token still exists
token = ConnectionToken.filter_not_expired(token=self.kwargs["token"]).first()
token = ConnectionToken.filter_not_expired(
token=self.kwargs["token"],
session__session__session_key=request.session.session_key,
).first()
if not token:
return redirect("authentik_core:if-user")
self.token = token

View File

@ -44,6 +44,7 @@ class RadiusProviderSerializer(ProviderSerializer):
"shared_secret",
"outpost_set",
"mfa_support",
"certificate",
]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs
@ -79,6 +80,7 @@ class RadiusOutpostConfigSerializer(ModelSerializer):
"client_networks",
"shared_secret",
"mfa_support",
"certificate",
]

View File

@ -0,0 +1,25 @@
# Generated by Django 5.1.9 on 2025-05-16 13:53
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0004_alter_certificatekeypair_name"),
("authentik_providers_radius", "0004_alter_radiusproviderpropertymapping_options"),
]
operations = [
migrations.AddField(
model_name="radiusprovider",
name="certificate",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authentik_crypto.certificatekeypair",
),
),
]

View File

@ -1,11 +1,14 @@
"""Radius Provider"""
from collections.abc import Iterable
from django.db import models
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.generators import generate_id
from authentik.outposts.models import OutpostModel
@ -38,6 +41,10 @@ class RadiusProvider(OutpostModel, Provider):
),
)
certificate = models.ForeignKey(
CertificateKeyPair, on_delete=models.CASCADE, default=None, null=True
)
@property
def launch_url(self) -> str | None:
"""Radius never has a launch URL"""
@ -57,6 +64,12 @@ class RadiusProvider(OutpostModel, Provider):
return RadiusProviderSerializer
def get_required_objects(self) -> Iterable[models.Model | str]:
required_models = [self, "authentik_stages_mtls.pass_outpost_certificate"]
if self.certificate is not None:
required_models.append(self.certificate)
return required_models
def __str__(self):
return f"Radius Provider {self.name}"

File diff suppressed because one or more lines are too long

View File

@ -27,7 +27,6 @@
</table>
</td>
</tr>
<td>
{% endblock %}
{% block sub_content %}

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 2025.6.2 Blueprint schema",
"title": "authentik 2025.6.3 Blueprint schema",
"required": [
"version",
"entries"
@ -8953,6 +8953,11 @@
"type": "boolean",
"title": "MFA Support",
"description": "When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon."
},
"certificate": {
"type": "string",
"format": "uuid",
"title": "Certificate"
}
},
"required": []

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
restart: unless-stopped
command: server
environment:
@ -55,7 +55,7 @@ services:
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
restart: unless-stopped
command: worker
environment:

2
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025062.6
goauthentik.io/api/v3 v3.2025063.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0

4
go.sum
View File

@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
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.2025062.6 h1:rlChhGP2vJufYCaTMb4sbRBEE1p2uL5T4HzMqF1AJ4A=
goauthentik.io/api/v3 v3.2025062.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025063.1 h1:zvKhZTESgMY/SNiLuTs7G0YleBnev1v7+S9Xd6PZ9bc=
goauthentik.io/api/v3 v3.2025063.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

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

View File

@ -34,9 +34,10 @@ var (
type SolverFunction func(*api.ChallengeTypes, api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error)
type FlowExecutor struct {
Params url.Values
Answers map[StageComponent]string
Context context.Context
Params url.Values
Answers map[StageComponent]string
Context context.Context
InteractiveSolver SolverFunction
solvers map[StageComponent]SolverFunction
@ -94,6 +95,10 @@ func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Config
return fe
}
func (fe *FlowExecutor) AddHeader(name string, value string) {
fe.api.GetConfig().AddDefaultHeader(name, value)
}
func (fe *FlowExecutor) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := fe.transport.RoundTrip(req)
if res != nil {
@ -110,7 +115,7 @@ func (fe *FlowExecutor) ApiClient() *api.APIClient {
return fe.api
}
type challengeCommon interface {
type ChallengeCommon interface {
GetComponent() string
GetResponseErrors() map[string][]api.ErrorDetail
}
@ -165,7 +170,7 @@ func (fe *FlowExecutor) getInitialChallenge() (*api.ChallengeTypes, error) {
if i == nil {
return nil, errors.New("response instance was null")
}
ch := i.(challengeCommon)
ch := i.(ChallengeCommon)
fe.log.WithField("component", ch.GetComponent()).Debug("Got challenge")
gcsp.SetTag("authentik.flow.component", ch.GetComponent())
gcsp.Finish()
@ -184,7 +189,7 @@ func (fe *FlowExecutor) solveFlowChallenge(challenge *api.ChallengeTypes, depth
if i == nil {
return false, errors.New("response request instance was null")
}
ch := i.(challengeCommon)
ch := i.(ChallengeCommon)
// Check for any validation errors that we might've gotten
if len(ch.GetResponseErrors()) > 0 {
@ -201,11 +206,17 @@ func (fe *FlowExecutor) solveFlowChallenge(challenge *api.ChallengeTypes, depth
case string(StageRedirect):
return true, nil
default:
solver, ok := fe.solvers[StageComponent(ch.GetComponent())]
if !ok {
return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent())
var err error
var rr api.FlowChallengeResponseRequest
if fe.InteractiveSolver != nil {
rr, err = fe.InteractiveSolver(challenge, responseReq)
} else {
solver, ok := fe.solvers[StageComponent(ch.GetComponent())]
if !ok {
return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent())
}
rr, err = solver(challenge, responseReq)
}
rr, err := solver(challenge, responseReq)
if err != nil {
return false, err
}
@ -220,7 +231,7 @@ func (fe *FlowExecutor) solveFlowChallenge(challenge *api.ChallengeTypes, depth
if i == nil {
return false, errors.New("response instance was null")
}
ch = i.(challengeCommon)
ch = i.(ChallengeCommon)
fe.log.WithField("component", ch.GetComponent()).Debug("Got response")
scsp.SetTag("authentik.flow.component", ch.GetComponent())
scsp.Finish()

View File

@ -8,6 +8,6 @@ import (
)
func TestConvert(t *testing.T) {
var a challengeCommon = api.NewIdentificationChallengeWithDefaults()
var a ChallengeCommon = api.NewIdentificationChallengeWithDefaults()
assert.NotNil(t, a)
}

View File

@ -9,6 +9,7 @@ import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
func parseCIDRs(raw string) []*net.IPNet {
@ -41,26 +42,28 @@ func (rs *RadiusServer) Refresh() error {
if len(apiProviders) < 1 {
return errors.New("no radius provider defined")
}
providers := make([]*ProviderInstance, len(apiProviders))
for idx, provider := range apiProviders {
providers := make(map[int32]*ProviderInstance)
for _, provider := range apiProviders {
existing, ok := rs.providers[provider.Pk]
state := map[string]*protocol.State{}
if ok {
state = existing.eapState
}
logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name)
providers[idx] = &ProviderInstance{
providers[provider.Pk] = &ProviderInstance{
SharedSecret: []byte(provider.GetSharedSecret()),
ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
MFASupport: provider.GetMfaSupport(),
appSlug: provider.ApplicationSlug,
flowSlug: provider.AuthFlowSlug,
certId: provider.GetCertificate(),
providerId: provider.Pk,
s: rs,
log: logger,
eapState: state,
}
}
rs.providers = providers
rs.log.Info("Update providers")
return nil
}
func (rs *RadiusServer) StartRadiusServer() error {
rs.log.WithField("listen", rs.s.Addr).Info("Starting radius server")
return rs.s.ListenAndServe()
}

View File

@ -0,0 +1,44 @@
# EAP protocol implementation
Install `eapol_test` (`sudo apt install eapoltest`)
Both PEAP and EAP-TLS require a minimal PKI setup. A CA, a certificate for the server and for EAP-TLS a client certificate need to be provided.
Save either of the config files below and run eapoltest like so:
```
# peap.conf is the config file under the PEAP testing section
# foo is the shared RADIUS secret
# 1.2.3.4 is the IP of the RADIUS server
eapol_test -c peap.conf -s foo -a 1.2.3.4
```
### PEAP testing
```
network={
ssid="DoesNotMatterForThisTest"
key_mgmt=WPA-EAP
eap=PEAP
identity="foo"
password="bar"
ca_cert="ca.pem"
phase2="auth=MSCHAPV2"
}
```
### EAP-TLS testing
```
network={
ssid="DoesNotMatterForThisTest"
key_mgmt=WPA-EAP
eap=TLS
identity="foo"
ca_cert="ca.pem"
client_cert="cert_client.pem"
private_key="cert_client.key"
eapol_flags=3
eap_workaround=0
}
```

View File

@ -0,0 +1,55 @@
package eap
import (
"fmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"layeh.com/radius"
)
type context struct {
req *radius.Request
rootPayload protocol.Payload
typeState map[protocol.Type]any
log *log.Entry
settings interface{}
parent *context
endStatus protocol.Status
handleInner func(protocol.Payload, protocol.StateManager, protocol.Context) (protocol.Payload, error)
}
func (ctx *context) RootPayload() protocol.Payload { return ctx.rootPayload }
func (ctx *context) Packet() *radius.Request { return ctx.req }
func (ctx *context) ProtocolSettings() any { return ctx.settings }
func (ctx *context) GetProtocolState(p protocol.Type) any { return ctx.typeState[p] }
func (ctx *context) SetProtocolState(p protocol.Type, st any) { ctx.typeState[p] = st }
func (ctx *context) IsProtocolStart(p protocol.Type) bool { return ctx.typeState[p] == nil }
func (ctx *context) Log() *log.Entry { return ctx.log }
func (ctx *context) HandleInnerEAP(p protocol.Payload, st protocol.StateManager) (protocol.Payload, error) {
return ctx.handleInner(p, st, ctx)
}
func (ctx *context) Inner(p protocol.Payload, t protocol.Type) protocol.Context {
nctx := &context{
req: ctx.req,
rootPayload: ctx.rootPayload,
typeState: ctx.typeState,
log: ctx.log.WithField("type", fmt.Sprintf("%T", p)).WithField("code", t),
settings: ctx.settings,
parent: ctx,
handleInner: ctx.handleInner,
}
nctx.log.Debug("Creating inner context")
return nctx
}
func (ctx *context) EndInnerProtocol(st protocol.Status) {
ctx.log.Info("Ending protocol")
if ctx.parent != nil {
ctx.parent.EndInnerProtocol(st)
return
}
if ctx.endStatus != protocol.StatusUnknown {
return
}
ctx.endStatus = st
}

View File

@ -0,0 +1,13 @@
package debug
import (
"fmt"
)
func FormatBytes(d []byte) string {
b := d
if len(b) > 32 {
b = b[:32]
}
return fmt.Sprintf("% x", b)
}

View File

@ -0,0 +1,182 @@
package eap
import (
"crypto/hmac"
"crypto/md5"
"encoding/base64"
"fmt"
"reflect"
"github.com/gorilla/securecookie"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/eap"
"goauthentik.io/internal/outpost/radius/eap/protocol/legacy_nak"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2869"
)
func sendErrorResponse(w radius.ResponseWriter, r *radius.Request) {
rres := r.Response(radius.CodeAccessReject)
err := w.Write(rres)
if err != nil {
log.WithError(err).Warning("failed to send response")
}
}
func (p *Packet) HandleRadiusPacket(w radius.ResponseWriter, r *radius.Request) {
p.r = r
rst := rfc2865.State_GetString(r.Packet)
if rst == "" {
rst = base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(12))
}
p.state = rst
rp := &Packet{r: r}
rep, err := p.handleEAP(p.eap, p.stm, nil)
rp.eap = rep
rres := r.Response(radius.CodeAccessReject)
if err == nil {
switch rp.eap.Code {
case protocol.CodeRequest:
rres.Code = radius.CodeAccessChallenge
case protocol.CodeFailure:
rres.Code = radius.CodeAccessReject
case protocol.CodeSuccess:
rres.Code = radius.CodeAccessAccept
}
} else {
rres.Code = radius.CodeAccessReject
log.WithError(err).Debug("Rejecting request")
}
for _, mod := range p.responseModifiers {
err := mod.ModifyRADIUSResponse(rres, r.Packet)
if err != nil {
log.WithError(err).Warning("Root-EAP: failed to modify response packet")
break
}
}
rfc2865.State_SetString(rres, p.state)
eapEncoded, err := rp.Encode()
if err != nil {
log.WithError(err).Warning("failed to encode response")
sendErrorResponse(w, r)
return
}
log.WithField("length", len(eapEncoded)).WithField("type", fmt.Sprintf("%T", rp.eap.Payload)).Debug("Root-EAP: encapsulated challenge")
rfc2869.EAPMessage_Set(rres, eapEncoded)
err = p.setMessageAuthenticator(rres)
if err != nil {
log.WithError(err).Warning("failed to send message authenticator")
sendErrorResponse(w, r)
return
}
err = w.Write(rres)
if err != nil {
log.WithError(err).Warning("failed to send response")
}
}
func (p *Packet) handleEAP(pp protocol.Payload, stm protocol.StateManager, parentContext *context) (*eap.Payload, error) {
st := stm.GetEAPState(p.state)
if st == nil {
log.Debug("Root-EAP: blank state")
st = protocol.BlankState(stm.GetEAPSettings())
}
nextChallengeToOffer, err := st.GetNextProtocol()
if err != nil {
return &eap.Payload{
Code: protocol.CodeFailure,
ID: p.eap.ID,
}, err
}
next := func() (*eap.Payload, error) {
st.ProtocolIndex += 1
st.TypeState = map[protocol.Type]any{}
stm.SetEAPState(p.state, st)
return p.handleEAP(pp, stm, nil)
}
if n, ok := pp.(*eap.Payload).Payload.(*legacy_nak.Payload); ok {
log.WithField("desired", n.DesiredType).Debug("Root-EAP: received NAK, trying next protocol")
pp.(*eap.Payload).Payload = nil
return next()
}
np, t, _ := eap.EmptyPayload(stm.GetEAPSettings(), nextChallengeToOffer)
var ctx *context
if parentContext != nil {
ctx = parentContext.Inner(np, t).(*context)
ctx.settings = stm.GetEAPSettings().ProtocolSettings[np.Type()]
} else {
ctx = &context{
req: p.r,
rootPayload: p.eap,
typeState: st.TypeState,
log: log.WithField("type", fmt.Sprintf("%T", np)).WithField("code", t),
settings: stm.GetEAPSettings().ProtocolSettings[t],
}
ctx.handleInner = func(pp protocol.Payload, sm protocol.StateManager, ctx protocol.Context) (protocol.Payload, error) {
// cctx := ctx.Inner(np, np.Type(), nil).(*context)
return p.handleEAP(pp, sm, ctx.(*context))
}
}
if !np.Offerable() {
ctx.Log().Debug("Root-EAP: protocol not offerable, skipping")
return next()
}
ctx.Log().Debug("Root-EAP: Passing to protocol")
res := &eap.Payload{
Code: protocol.CodeRequest,
ID: p.eap.ID + 1,
MsgType: t,
}
var payload any
if reflect.TypeOf(pp.(*eap.Payload).Payload) == reflect.TypeOf(np) {
np.Decode(pp.(*eap.Payload).RawPayload)
}
payload = np.Handle(ctx)
if payload != nil {
res.Payload = payload.(protocol.Payload)
}
stm.SetEAPState(p.state, st)
if rm, ok := np.(protocol.ResponseModifier); ok {
ctx.log.Debug("Root-EAP: Registered response modifier")
p.responseModifiers = append(p.responseModifiers, rm)
}
switch ctx.endStatus {
case protocol.StatusSuccess:
res.Code = protocol.CodeSuccess
res.ID -= 1
case protocol.StatusError:
res.Code = protocol.CodeFailure
res.ID -= 1
case protocol.StatusNextProtocol:
ctx.log.Debug("Root-EAP: Protocol ended, starting next protocol")
return next()
case protocol.StatusUnknown:
}
return res, nil
}
func (p *Packet) setMessageAuthenticator(rp *radius.Packet) error {
_ = rfc2869.MessageAuthenticator_Set(rp, make([]byte, 16))
hash := hmac.New(md5.New, rp.Secret)
encode, err := rp.MarshalBinary()
if err != nil {
return err
}
hash.Write(encode)
_ = rfc2869.MessageAuthenticator_Set(rp, hash.Sum(nil))
return nil
}

View File

@ -0,0 +1,34 @@
package eap
import (
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/eap"
"layeh.com/radius"
)
type Packet struct {
r *radius.Request
eap *eap.Payload
stm protocol.StateManager
state string
responseModifiers []protocol.ResponseModifier
}
func Decode(stm protocol.StateManager, raw []byte) (*Packet, error) {
packet := &Packet{
eap: &eap.Payload{
Settings: stm.GetEAPSettings(),
},
stm: stm,
responseModifiers: []protocol.ResponseModifier{},
}
err := packet.eap.Decode(raw)
if err != nil {
return nil, err
}
return packet, nil
}
func (p *Packet) Encode() ([]byte, error) {
return p.eap.Encode()
}

View File

@ -0,0 +1,32 @@
package protocol
import (
log "github.com/sirupsen/logrus"
"layeh.com/radius"
)
type Status int
const (
StatusUnknown Status = iota
StatusSuccess
StatusError
StatusNextProtocol
)
type Context interface {
Packet() *radius.Request
RootPayload() Payload
ProtocolSettings() interface{}
GetProtocolState(p Type) interface{}
SetProtocolState(p Type, s interface{})
IsProtocolStart(p Type) bool
HandleInnerEAP(Payload, StateManager) (Payload, error)
Inner(Payload, Type) Context
EndInnerProtocol(Status)
Log() *log.Entry
}

View File

@ -0,0 +1,23 @@
package eap
import (
"fmt"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
func EmptyPayload(settings protocol.Settings, t protocol.Type) (protocol.Payload, protocol.Type, error) {
for _, cons := range settings.Protocols {
np := cons()
if np.Type() == t {
return np, np.Type(), nil
}
// If the protocol has an inner protocol, return the original type but the code for the inner protocol
if i, ok := np.(protocol.Inner); ok {
if ii := i.HasInner(); ii != nil {
return np, ii.Type(), nil
}
}
}
return nil, protocol.Type(0), fmt.Errorf("unsupported EAP type %d", t)
}

View File

@ -0,0 +1,96 @@
package eap
import (
"encoding/binary"
"fmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
const TypeEAP protocol.Type = 0
func Protocol() protocol.Payload {
return &Payload{}
}
type Payload struct {
Code protocol.Code
ID uint8
Length uint16
MsgType protocol.Type
Payload protocol.Payload
RawPayload []byte
Settings protocol.Settings
}
func (p *Payload) Type() protocol.Type {
return TypeEAP
}
func (p *Payload) Offerable() bool {
return false
}
func (p *Payload) Decode(raw []byte) error {
p.Code = protocol.Code(raw[0])
p.ID = raw[1]
p.Length = binary.BigEndian.Uint16(raw[2:])
if p.Length != uint16(len(raw)) {
return fmt.Errorf("mismatched packet length; got %d, expected %d", p.Length, uint16(len(raw)))
}
if len(raw) > 4 && (p.Code == protocol.CodeRequest || p.Code == protocol.CodeResponse) {
p.MsgType = protocol.Type(raw[4])
}
log.WithField("raw", debug.FormatBytes(raw)).Trace("EAP: decode raw")
p.RawPayload = raw[5:]
if p.Payload == nil {
pp, _, err := EmptyPayload(p.Settings, p.MsgType)
if err != nil {
return err
}
p.Payload = pp
}
err := p.Payload.Decode(raw[5:])
if err != nil {
return err
}
return nil
}
func (p *Payload) Encode() ([]byte, error) {
buff := make([]byte, 4)
buff[0] = uint8(p.Code)
buff[1] = uint8(p.ID)
if p.Payload != nil {
payloadBuffer, err := p.Payload.Encode()
if err != nil {
return buff, err
}
if p.Code == protocol.CodeRequest || p.Code == protocol.CodeResponse {
buff = append(buff, uint8(p.MsgType))
}
buff = append(buff, payloadBuffer...)
}
binary.BigEndian.PutUint16(buff[2:], uint16(len(buff)))
return buff, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
ctx.Log().Debug("EAP: Handle")
return nil
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<EAP Packet Code=%d, ID=%d, Type=%d, Length=%d, Payload=%T>",
p.Code,
p.ID,
p.MsgType,
p.Length,
p.Payload,
)
}

View File

@ -0,0 +1,5 @@
package eap
type State struct {
PacketID uint8
}

View File

@ -0,0 +1,61 @@
package gtc
import (
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
const TypeGTC protocol.Type = 6
func Protocol() protocol.Payload {
return &Payload{}
}
type Payload struct {
Challenge []byte
st *State
raw []byte
}
func (p *Payload) Type() protocol.Type {
return TypeGTC
}
func (p *Payload) Decode(raw []byte) error {
p.raw = raw
return nil
}
func (p *Payload) Encode() ([]byte, error) {
return p.Challenge, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
defer func() {
ctx.SetProtocolState(TypeGTC, p.st)
}()
settings := ctx.ProtocolSettings().(Settings)
if ctx.IsProtocolStart(TypeGTC) {
g, v := settings.ChallengeHandler(ctx)
p.st = &State{
getChallenge: g,
validateResponse: v,
}
return &Payload{
Challenge: p.st.getChallenge(),
}
}
p.st = ctx.GetProtocolState(TypeGTC).(*State)
p.st.validateResponse(p.raw)
return &Payload{
Challenge: p.st.getChallenge(),
}
}
func (p *Payload) Offerable() bool {
return true
}
func (p *Payload) String() string {
return "<GTC Packet>"
}

View File

@ -0,0 +1,10 @@
package gtc
import "goauthentik.io/internal/outpost/radius/eap/protocol"
type GetChallenge func() []byte
type ValidateResponse func(answer []byte)
type Settings struct {
ChallengeHandler func(ctx protocol.Context) (GetChallenge, ValidateResponse)
}

View File

@ -0,0 +1,6 @@
package gtc
type State struct {
getChallenge GetChallenge
validateResponse ValidateResponse
}

View File

@ -0,0 +1,48 @@
package identity
import (
"fmt"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
const TypeIdentity protocol.Type = 1
func Protocol() protocol.Payload {
return &Payload{}
}
type Payload struct {
Identity string
}
func (p *Payload) Type() protocol.Type {
return TypeIdentity
}
func (p *Payload) Decode(raw []byte) error {
p.Identity = string(raw)
return nil
}
func (p *Payload) Encode() ([]byte, error) {
return []byte{}, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
if ctx.IsProtocolStart(TypeIdentity) {
ctx.EndInnerProtocol(protocol.StatusNextProtocol)
}
return nil
}
func (p *Payload) Offerable() bool {
return false
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<Identity Packet Identity=%s>",
p.Identity,
)
}

View File

@ -0,0 +1,48 @@
package legacy_nak
import (
"fmt"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
const TypeLegacyNAK protocol.Type = 3
func Protocol() protocol.Payload {
return &Payload{}
}
type Payload struct {
DesiredType protocol.Type
}
func (p *Payload) Type() protocol.Type {
return TypeLegacyNAK
}
func (p *Payload) Decode(raw []byte) error {
p.DesiredType = protocol.Type(raw[0])
return nil
}
func (p *Payload) Encode() ([]byte, error) {
return []byte{byte(p.DesiredType)}, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
if ctx.IsProtocolStart(TypeLegacyNAK) {
ctx.EndInnerProtocol(protocol.StatusError)
}
return nil
}
func (p *Payload) Offerable() bool {
return false
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<Legacy NAK Packet DesiredType=%d>",
p.DesiredType,
)
}

View File

@ -0,0 +1,23 @@
package mschapv2
import (
"bytes"
"errors"
)
type Response struct {
Challenge []byte
NTResponse []byte
Flags uint8
}
func ParseResponse(raw []byte) (*Response, error) {
res := &Response{}
res.Challenge = raw[:challengeValueSize]
if !bytes.Equal(raw[challengeValueSize:challengeValueSize+responseReservedSize], make([]byte, 8)) {
return nil, errors.New("MSCHAPv2: Reserved bytes not empty?")
}
res.NTResponse = raw[challengeValueSize+responseReservedSize : challengeValueSize+responseReservedSize+responseNTResponseSize]
res.Flags = (raw[challengeValueSize+responseReservedSize+responseNTResponseSize])
return res, nil
}

View File

@ -0,0 +1,23 @@
package mschapv2
import "encoding/binary"
type SuccessRequest struct {
*Payload
Authenticator []byte
}
// A success request is encoded slightly differently, it doesn't have a challenge and as such
// doesn't need to encode the length of it
func (sr *SuccessRequest) Encode() ([]byte, error) {
encoded := []byte{
byte(sr.OpCode),
sr.MSCHAPv2ID,
0,
0,
}
encoded = append(encoded, sr.Authenticator...)
sr.MSLength = uint16(len(encoded))
binary.BigEndian.PutUint16(encoded[2:], sr.MSLength)
return encoded, nil
}

View File

@ -0,0 +1,196 @@
package mschapv2
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/gorilla/securecookie"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/eap"
"goauthentik.io/internal/outpost/radius/eap/protocol/peap"
"layeh.com/radius"
"layeh.com/radius/vendors/microsoft"
)
const TypeMSCHAPv2 protocol.Type = 26
func Protocol() protocol.Payload {
return &Payload{}
}
const (
challengeValueSize = 16
responseValueSize = 49
responseReservedSize = 8
responseNTResponseSize = 24
)
type OpCode uint8
const (
OpChallenge OpCode = 1
OpResponse OpCode = 2
OpSuccess OpCode = 3
)
type Payload struct {
OpCode OpCode
MSCHAPv2ID uint8
MSLength uint16
ValueSize uint8
Challenge []byte
Response []byte
Name []byte
st *State
}
func (p *Payload) Type() protocol.Type {
return TypeMSCHAPv2
}
func (p *Payload) Decode(raw []byte) error {
log.WithField("raw", debug.FormatBytes(raw)).Debugf("MSCHAPv2: decode raw")
p.OpCode = OpCode(raw[0])
if p.OpCode == OpSuccess {
return nil
}
// TODO: Validate against root EAP packet
p.MSCHAPv2ID = raw[1]
p.MSLength = binary.BigEndian.Uint16(raw[2:])
p.ValueSize = raw[4]
if p.ValueSize != responseValueSize {
return fmt.Errorf("MSCHAPv2: incorrect value size: %d", p.ValueSize)
}
p.Response = raw[5 : p.ValueSize+5]
p.Name = raw[5+p.ValueSize:]
if int(p.MSLength) != len(raw) {
return fmt.Errorf("MSCHAPv2: incorrect MS-Length: %d, should be %d", p.MSLength, len(raw))
}
return nil
}
func (p *Payload) Encode() ([]byte, error) {
encoded := []byte{
byte(p.OpCode),
p.MSCHAPv2ID,
0,
0,
byte(len(p.Challenge)),
}
encoded = append(encoded, p.Challenge...)
encoded = append(encoded, p.Name...)
p.MSLength = uint16(len(encoded))
binary.BigEndian.PutUint16(encoded[2:], p.MSLength)
return encoded, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
defer func() {
ctx.SetProtocolState(TypeMSCHAPv2, p.st)
}()
rootEap := ctx.RootPayload().(*eap.Payload)
if ctx.IsProtocolStart(TypeMSCHAPv2) {
ctx.Log().Debug("MSCHAPv2: Empty state, starting")
p.st = &State{
Challenge: securecookie.GenerateRandomKey(challengeValueSize),
}
return &Payload{
OpCode: OpChallenge,
MSCHAPv2ID: rootEap.ID + 1,
Challenge: p.st.Challenge,
Name: []byte("authentik"),
}
}
p.st = ctx.GetProtocolState(TypeMSCHAPv2).(*State)
response := &Payload{
MSCHAPv2ID: rootEap.ID + 1,
}
settings := ctx.ProtocolSettings().(Settings)
ctx.Log().Debugf("MSCHAPv2: OpCode: %d", p.OpCode)
if p.OpCode == OpResponse {
res, err := ParseResponse(p.Response)
if err != nil {
ctx.Log().WithError(err).Warning("MSCHAPv2: failed to parse response")
return nil
}
p.st.PeerChallenge = res.Challenge
auth, err := settings.AuthenticateRequest(AuthRequest{
Challenge: p.st.Challenge,
PeerChallenge: p.st.PeerChallenge,
})
if err != nil {
ctx.Log().WithError(err).Warning("MSCHAPv2: failed to check password")
return nil
}
if !bytes.Equal(auth.NTResponse, res.NTResponse) {
ctx.Log().Warning("MSCHAPv2: NT response mismatch")
return nil
}
ctx.Log().Info("MSCHAPv2: Successfully checked password")
p.st.AuthResponse = auth
succ := &SuccessRequest{
Payload: &Payload{
OpCode: OpSuccess,
},
Authenticator: []byte(auth.AuthenticatorResponse),
}
return succ
} else if p.OpCode == OpSuccess && p.st.AuthResponse != nil {
ep := &peap.ExtensionPayload{
AVPs: []peap.ExtensionAVP{
{
Mandatory: true,
Type: peap.AVPAckResult,
Value: []byte{0, 1},
},
},
}
p.st.IsProtocolEnded = true
return ep
} else if p.st.IsProtocolEnded {
ctx.EndInnerProtocol(protocol.StatusSuccess)
return &Payload{}
}
return response
}
func (p *Payload) ModifyRADIUSResponse(r *radius.Packet, q *radius.Packet) error {
if p.st == nil || p.st.AuthResponse == nil {
return nil
}
if r.Code != radius.CodeAccessAccept {
return nil
}
log.Debug("MSCHAPv2: Radius modifier")
if len(microsoft.MSMPPERecvKey_Get(r, q)) < 1 {
microsoft.MSMPPERecvKey_Set(r, p.st.AuthResponse.RecvKey)
}
if len(microsoft.MSMPPESendKey_Get(r, q)) < 1 {
microsoft.MSMPPESendKey_Set(r, p.st.AuthResponse.SendKey)
}
return nil
}
func (p *Payload) Offerable() bool {
return true
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<MSCHAPv2 Packet OpCode=%d, MSCHAPv2ID=%d>",
p.OpCode,
p.MSCHAPv2ID,
)
}

View File

@ -0,0 +1,50 @@
package mschapv2
import (
"layeh.com/radius/rfc2759"
"layeh.com/radius/rfc3079"
)
type Settings struct {
AuthenticateRequest func(req AuthRequest) (*AuthResponse, error)
}
type AuthRequest struct {
Challenge []byte
PeerChallenge []byte
}
type AuthResponse struct {
NTResponse []byte
RecvKey []byte
SendKey []byte
AuthenticatorResponse string
}
func DebugStaticCredentials(user, password []byte) func(req AuthRequest) (*AuthResponse, error) {
return func(req AuthRequest) (*AuthResponse, error) {
res := &AuthResponse{}
ntResponse, err := rfc2759.GenerateNTResponse(req.Challenge, req.PeerChallenge, user, password)
if err != nil {
return nil, err
}
res.NTResponse = ntResponse
res.RecvKey, err = rfc3079.MakeKey(ntResponse, password, false)
if err != nil {
return nil, err
}
res.SendKey, err = rfc3079.MakeKey(ntResponse, password, true)
if err != nil {
return nil, err
}
res.AuthenticatorResponse, err = rfc2759.GenerateAuthenticatorResponse(req.Challenge, req.PeerChallenge, ntResponse, user, password)
if err != nil {
return nil, err
}
return res, nil
}
}

View File

@ -0,0 +1,8 @@
package mschapv2
type State struct {
Challenge []byte
PeerChallenge []byte
IsProtocolEnded bool
AuthResponse *AuthResponse
}

View File

@ -0,0 +1,31 @@
package protocol
import "layeh.com/radius"
type Type uint8
type Code uint8
const (
CodeRequest Code = 1
CodeResponse Code = 2
CodeSuccess Code = 3
CodeFailure Code = 4
)
type Payload interface {
Decode(raw []byte) error
Encode() ([]byte, error)
Handle(ctx Context) Payload
Type() Type
Offerable() bool
String() string
}
type Inner interface {
HasInner() Payload
}
type ResponseModifier interface {
ModifyRADIUSResponse(r *radius.Packet, q *radius.Packet) error
}

View File

@ -0,0 +1,59 @@
package peap
import (
"encoding/binary"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
const TypePEAPExtension protocol.Type = 33
type ExtensionPayload struct {
AVPs []ExtensionAVP
}
func (ep *ExtensionPayload) Decode(raw []byte) error {
log.WithField("raw", debug.FormatBytes(raw)).Debugf("PEAP-Extension: decode raw")
ep.AVPs = []ExtensionAVP{}
offset := 0
for {
if len(raw[offset:]) < 4 {
return nil
}
len := binary.BigEndian.Uint16(raw[offset+2:offset+2+2]) + ExtensionHeaderSize
avp := &ExtensionAVP{}
err := avp.Decode(raw[offset : offset+int(len)])
if err != nil {
return err
}
ep.AVPs = append(ep.AVPs, *avp)
offset = offset + int(len)
}
}
func (ep *ExtensionPayload) Encode() ([]byte, error) {
log.Debug("PEAP-Extension: encode")
buff := []byte{}
for _, avp := range ep.AVPs {
buff = append(buff, avp.Encode()...)
}
return buff, nil
}
func (ep *ExtensionPayload) Handle(protocol.Context) protocol.Payload {
return nil
}
func (ep *ExtensionPayload) Offerable() bool {
return false
}
func (ep *ExtensionPayload) String() string {
return "<PEAP Extension Payload>"
}
func (ep *ExtensionPayload) Type() protocol.Type {
return TypePEAPExtension
}

View File

@ -0,0 +1,62 @@
package peap
import (
"encoding/binary"
"errors"
"fmt"
)
type AVPType uint16
const (
AVPAckResult AVPType = 3
)
const ExtensionHeaderSize = 4
type ExtensionAVP struct {
Mandatory bool
Type AVPType // 14-bit field
Length uint16
Value []byte
}
var (
ErrorReservedBitSet = errors.New("PEAP-Extension: Reserved bit is not 0")
)
func (eavp *ExtensionAVP) Decode(raw []byte) error {
typ := binary.BigEndian.Uint16(raw[:2])
if typ>>15 == 1 {
eavp.Mandatory = true
}
if typ>>14&1 != 0 {
return ErrorReservedBitSet
}
eavp.Type = AVPType(typ & 0b0011111111111111)
eavp.Length = binary.BigEndian.Uint16(raw[2:4])
val := raw[4:]
if eavp.Length != uint16(len(val)) {
return fmt.Errorf("PEAP-Extension: Invalid length: %d, should be %d", eavp.Length, len(val))
}
return nil
}
func (eavp ExtensionAVP) Encode() []byte {
buff := []byte{
0,
0,
0,
0,
}
t := uint16(eavp.Type)
// Type is a 14-bit number, the highest bit is the mandatory flag
if eavp.Mandatory {
t = t | 0b1000000000000000
}
// The next bit is reserved and should always be set to 0
t = t & 0b1011111111111111
binary.BigEndian.PutUint16(buff[0:], t)
binary.BigEndian.PutUint16(buff[2:], uint16(len(eavp.Value)))
return append(buff, eavp.Value...)
}

View File

@ -0,0 +1,36 @@
package peap_test
import (
"testing"
"github.com/stretchr/testify/assert"
"goauthentik.io/internal/outpost/radius/eap/protocol/peap"
)
func TestEncode(t *testing.T) {
eavp := peap.ExtensionAVP{
Mandatory: true,
Type: peap.AVPType(3),
}
assert.Equal(t, []byte{0x80, 0x3, 0x0, 0x0}, eavp.Encode())
}
func TestDecode(t *testing.T) {
eavp := peap.ExtensionAVP{}
err := eavp.Decode([]byte{0x80, 0x3, 0x0, 0x0})
assert.NoError(t, err)
assert.True(t, eavp.Mandatory)
assert.Equal(t, peap.AVPType(3), eavp.Type)
}
func TestDecode_Invalid_ReservedBitSet(t *testing.T) {
eavp := peap.ExtensionAVP{}
err := eavp.Decode([]byte{0xc0, 0x3, 0x0, 0x0})
assert.ErrorIs(t, err, peap.ErrorReservedBitSet)
}
func TestDecode_Invalid_Length(t *testing.T) {
eavp := peap.ExtensionAVP{}
err := eavp.Decode([]byte{0x80, 0x3, 0x0, 0x0, 0x0})
assert.NotNil(t, err)
}

View File

@ -0,0 +1,167 @@
package peap
import (
"encoding/binary"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/eap"
"goauthentik.io/internal/outpost/radius/eap/protocol/identity"
"goauthentik.io/internal/outpost/radius/eap/protocol/tls"
)
const TypePEAP protocol.Type = 25
func Protocol() protocol.Payload {
return &tls.Payload{
Inner: &Payload{
Inner: &eap.Payload{},
},
}
}
type Payload struct {
Inner protocol.Payload
eap *eap.Payload
st *State
settings Settings
raw []byte
}
func (p *Payload) Type() protocol.Type {
return TypePEAP
}
func (p *Payload) HasInner() protocol.Payload {
return p.Inner
}
func (p *Payload) Decode(raw []byte) error {
log.WithField("raw", debug.FormatBytes(raw)).Debug("PEAP: Decode")
p.raw = raw
return nil
}
// Inner EAP packets in PEAP may not include the header, hence we need a custom decoder
// https://datatracker.ietf.org/doc/html/draft-kamath-pppext-peapv0-00.txt#section-1.1
func (p *Payload) Encode() ([]byte, error) {
log.Debug("PEAP: Encoding inner EAP")
if p.eap.Payload == nil {
return []byte{}, errors.New("PEAP: no payload in response eap packet")
}
payload, err := p.eap.Payload.Encode()
if err != nil {
return []byte{}, err
}
encoded := []byte{
byte(p.eap.MsgType),
}
return append(encoded, payload...), nil
}
// Inner EAP packets in PEAP may not include the header, hence we need a custom decoder
// https://datatracker.ietf.org/doc/html/draft-kamath-pppext-peapv0-00.txt#section-1.1
func (p *Payload) eapInnerDecode(ctx protocol.Context) (*eap.Payload, error) {
ep := &eap.Payload{
Settings: p.GetEAPSettings(),
}
rootEap := ctx.RootPayload().(*eap.Payload)
fixedRaw := []byte{
byte(rootEap.Code),
rootEap.ID,
// 2 byte space for length
0,
0,
}
fullLength := len(p.raw) + len(fixedRaw)
binary.BigEndian.PutUint16(fixedRaw[2:], uint16(fullLength))
fixedRaw = append(fixedRaw, p.raw...)
// If the raw data has a msgtype set to type 33 (EAP extension), decode differently
if len(p.raw) > 5 && p.raw[4] == byte(TypePEAPExtension) {
ep.Payload = &ExtensionPayload{}
// Pass original raw data to EAP as extension payloads are encoded like normal EAP packets
fixedRaw = p.raw
}
err := ep.Decode(fixedRaw)
if err != nil {
return nil, err
}
return ep, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
defer func() {
ctx.SetProtocolState(TypePEAP, p.st)
}()
p.settings = ctx.ProtocolSettings().(Settings)
rootEap := ctx.RootPayload().(*eap.Payload)
if ctx.IsProtocolStart(TypePEAP) {
ctx.Log().Debug("PEAP: Protocol start")
p.st = &State{
SubState: make(map[string]*protocol.State),
}
return &eap.Payload{
Code: protocol.CodeRequest,
ID: rootEap.ID + 1,
MsgType: identity.TypeIdentity,
Payload: &identity.Payload{},
}
}
p.st = ctx.GetProtocolState(TypePEAP).(*State)
ep, err := p.eapInnerDecode(ctx)
if err != nil {
ctx.Log().WithError(err).Warning("PEAP: failed to decode inner EAP")
return &eap.Payload{
Code: protocol.CodeFailure,
ID: rootEap.ID + 1,
}
}
p.eap = ep
ctx.Log().Debugf("PEAP: Decoded inner EAP to %s", ep.String())
res, err := ctx.HandleInnerEAP(ep, p)
if err != nil {
ctx.Log().WithError(err).Warning("PEAP: failed to handle inner EAP")
return nil
}
// Normal payloads need to be wrapped in PEAP to use the correct encoding (see Encode() above)
// Extension payloads handle encoding differently
pres := res.(*eap.Payload)
if _, ok := pres.Payload.(*ExtensionPayload); ok {
// HandleInnerEAP will set the MsgType to the PEAP type, however we need to override that
pres.MsgType = TypePEAPExtension
ctx.Log().Debug("PEAP: Encoding response as extension")
return res
}
return &Payload{eap: pres}
}
func (p *Payload) GetEAPSettings() protocol.Settings {
return p.settings.InnerProtocols
}
func (p *Payload) GetEAPState(key string) *protocol.State {
return p.st.SubState[key]
}
func (p *Payload) SetEAPState(key string, st *protocol.State) {
p.st.SubState[key] = st
}
func (p *Payload) Offerable() bool {
return true
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<PEAP Packet Wrapping=%s>",
p.eap.String(),
)
}

View File

@ -0,0 +1,16 @@
package peap
import (
"crypto/tls"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
type Settings struct {
Config *tls.Config
InnerProtocols protocol.Settings
}
func (s Settings) TLSConfig() *tls.Config {
return s.Config
}

View File

@ -0,0 +1,7 @@
package peap
import "goauthentik.io/internal/outpost/radius/eap/protocol"
type State struct {
SubState map[string]*protocol.State
}

View File

@ -0,0 +1,42 @@
package protocol
import (
"errors"
"slices"
)
type StateManager interface {
GetEAPSettings() Settings
GetEAPState(string) *State
SetEAPState(string, *State)
}
type ProtocolConstructor func() Payload
type Settings struct {
Protocols []ProtocolConstructor
ProtocolPriority []Type
ProtocolSettings map[Type]interface{}
}
type State struct {
Protocols []ProtocolConstructor
ProtocolIndex int
ProtocolPriority []Type
TypeState map[Type]any
}
func (st *State) GetNextProtocol() (Type, error) {
if st.ProtocolIndex >= len(st.ProtocolPriority) {
return Type(0), errors.New("no more protocols to offer")
}
return st.ProtocolPriority[st.ProtocolIndex], nil
}
func BlankState(settings Settings) *State {
return &State{
Protocols: slices.Clone(settings.Protocols),
ProtocolPriority: slices.Clone(settings.ProtocolPriority),
TypeState: map[Type]any{},
}
}

View File

@ -0,0 +1,111 @@
package tls
import (
"bytes"
"context"
"errors"
"net"
"time"
"github.com/avast/retry-go/v4"
log "github.com/sirupsen/logrus"
)
type BuffConn struct {
reader *bytes.Buffer
writer *bytes.Buffer
ctx context.Context
expectedWriterByteCount int
writtenByteCount int
retryOptions []retry.Option
}
func NewBuffConn(initialData []byte, ctx context.Context) *BuffConn {
c := &BuffConn{
reader: bytes.NewBuffer(initialData),
writer: bytes.NewBuffer([]byte{}),
ctx: ctx,
retryOptions: []retry.Option{
retry.Context(ctx),
retry.Delay(10 * time.Microsecond),
retry.DelayType(retry.BackOffDelay),
retry.MaxDelay(100 * time.Millisecond),
retry.Attempts(0),
},
}
return c
}
var errStall = errors.New("Stall")
func (conn BuffConn) OutboundData() []byte {
d, _ := retry.DoWithData(
func() ([]byte, error) {
b := conn.writer.Bytes()
if len(b) < 1 {
return nil, errStall
}
return b, nil
},
conn.retryOptions...,
)
return d
}
func (conn *BuffConn) UpdateData(data []byte) {
conn.reader.Write(data)
conn.writtenByteCount += len(data)
log.Debugf("TLS(buffcon): Appending new data %d (total %d, expecting %d)", len(data), conn.writtenByteCount, conn.expectedWriterByteCount)
}
func (conn BuffConn) NeedsMoreData() bool {
if conn.expectedWriterByteCount > 0 {
return conn.reader.Len() < int(conn.expectedWriterByteCount)
}
return false
}
func (conn *BuffConn) Read(p []byte) (int, error) {
d, err := retry.DoWithData(
func() (int, error) {
if conn.reader.Len() == 0 {
log.Debugf("TLS(buffcon): Attempted read %d from empty buffer, stalling...", len(p))
return 0, errStall
}
if conn.expectedWriterByteCount > 0 {
// If we're waiting for more data, we need to stall
if conn.writtenByteCount < int(conn.expectedWriterByteCount) {
log.Debugf("TLS(buffcon): Attempted read %d while waiting for bytes %d, stalling...", len(p), conn.expectedWriterByteCount-conn.reader.Len())
return 0, errStall
}
// If we have all the data, reset how much we're expecting to still get
if conn.writtenByteCount == int(conn.expectedWriterByteCount) {
conn.expectedWriterByteCount = 0
}
}
if conn.reader.Len() == 0 {
conn.writtenByteCount = 0
}
n, err := conn.reader.Read(p)
log.Debugf("TLS(buffcon): Read: %d into %d (total %d)", n, len(p), conn.reader.Len())
return n, err
},
conn.retryOptions...,
)
return d, err
}
func (conn BuffConn) Write(p []byte) (int, error) {
log.Debugf("TLS(buffcon): Write: %d", len(p))
return conn.writer.Write(p)
}
func (conn BuffConn) Close() error { return nil }
func (conn BuffConn) LocalAddr() net.Addr { return nil }
func (conn BuffConn) RemoteAddr() net.Addr { return nil }
func (conn BuffConn) SetDeadline(t time.Time) error { return nil }
func (conn BuffConn) SetReadDeadline(t time.Time) error { return nil }
func (conn BuffConn) SetWriteDeadline(t time.Time) error { return nil }

View File

@ -0,0 +1,10 @@
package tls
type Flag byte
const (
FlagLengthIncluded Flag = 1 << 7
FlagMoreFragments Flag = 1 << 6
FlagTLSStart Flag = 1 << 5
FlagNone Flag = 0
)

View File

@ -0,0 +1,39 @@
package tls
import (
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
func (p *Payload) innerHandler(ctx protocol.Context) {
d := make([]byte, 1024)
if !ctx.IsProtocolStart(p.Inner.Type()) {
ctx.Log().Debug("TLS: Reading from TLS for inner protocol")
n, err := p.st.TLS.Read(d)
if err != nil {
ctx.Log().WithError(err).Warning("TLS: Failed to read from TLS connection")
ctx.EndInnerProtocol(protocol.StatusError)
return
}
// Truncate data to the size we read
d = d[:n]
}
err := p.Inner.Decode(d)
if err != nil {
ctx.Log().WithError(err).Warning("TLS: failed to decode inner protocol")
ctx.EndInnerProtocol(protocol.StatusError)
return
}
pl := p.Inner.Handle(ctx.Inner(p.Inner, p.Inner.Type()))
enc, err := pl.Encode()
if err != nil {
ctx.Log().WithError(err).Warning("TLS: failed to encode inner protocol")
ctx.EndInnerProtocol(protocol.StatusError)
return
}
_, err = p.st.TLS.Write(enc)
if err != nil {
ctx.Log().WithError(err).Warning("TLS: failed to write to TLS")
ctx.EndInnerProtocol(protocol.StatusError)
return
}
}

View File

@ -0,0 +1,279 @@
package tls
import (
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"os"
"slices"
"time"
"github.com/avast/retry-go/v4"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"layeh.com/radius"
"layeh.com/radius/vendors/microsoft"
)
const maxChunkSize = 1000
const staleConnectionTimeout = 10
const TypeTLS protocol.Type = 13
func Protocol() protocol.Payload {
return &Payload{}
}
type Payload struct {
Flags Flag
Length uint32
Data []byte
st *State
Inner protocol.Payload
}
func (p *Payload) Type() protocol.Type {
return TypeTLS
}
func (p *Payload) HasInner() protocol.Payload {
return p.Inner
}
func (p *Payload) Offerable() bool {
return true
}
func (p *Payload) Decode(raw []byte) error {
p.Flags = Flag(raw[0])
raw = raw[1:]
if p.Flags&FlagLengthIncluded != 0 {
if len(raw) < 4 {
return errors.New("invalid size")
}
p.Length = binary.BigEndian.Uint32(raw)
p.Data = raw[4:]
} else {
p.Data = raw[0:]
}
log.WithField("raw", debug.FormatBytes(p.Data)).WithField("size", len(p.Data)).WithField("flags", p.Flags).Trace("TLS: decode raw")
return nil
}
func (p *Payload) Encode() ([]byte, error) {
l := 1
if p.Flags&FlagLengthIncluded != 0 {
l += 4
}
buff := make([]byte, len(p.Data)+l)
buff[0] = byte(p.Flags)
if p.Flags&FlagLengthIncluded != 0 {
buff[1] = byte(p.Length >> 24)
buff[2] = byte(p.Length >> 16)
buff[3] = byte(p.Length >> 8)
buff[4] = byte(p.Length)
}
if len(p.Data) > 0 {
copy(buff[5:], p.Data)
}
return buff, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
defer func() {
ctx.SetProtocolState(TypeTLS, p.st)
}()
if ctx.IsProtocolStart(TypeTLS) {
p.st = NewState(ctx).(*State)
return &Payload{
Flags: FlagTLSStart,
}
}
p.st = ctx.GetProtocolState(TypeTLS).(*State)
if p.st.TLS == nil {
p.tlsInit(ctx)
} else if len(p.Data) > 0 {
ctx.Log().Debug("TLS: Updating buffer with new TLS data from packet")
if p.Flags&FlagLengthIncluded != 0 && p.st.Conn.expectedWriterByteCount == 0 {
ctx.Log().Debugf("TLS: Expecting %d total bytes, will buffer", p.Length)
p.st.Conn.expectedWriterByteCount = int(p.Length)
} else if p.Flags&FlagLengthIncluded != 0 {
ctx.Log().Debug("TLS: No length included, not buffering")
p.st.Conn.expectedWriterByteCount = 0
}
p.st.Conn.UpdateData(p.Data)
if !p.st.Conn.NeedsMoreData() && !p.st.HandshakeDone {
// Wait for outbound data to be available
p.st.Conn.OutboundData()
}
}
// If we need more data, send the client the go-ahead
if p.st.Conn.NeedsMoreData() {
return &Payload{
Flags: FlagNone,
Length: 0,
Data: []byte{},
}
}
if p.st.HasMore() {
return p.sendNextChunk()
}
if p.st.Conn.writer.Len() == 0 && p.st.HandshakeDone {
if p.Inner != nil {
ctx.Log().Debug("TLS: Handshake is done, delegating to inner protocol")
p.innerHandler(ctx)
return p.startChunkedTransfer(p.st.Conn.OutboundData())
}
defer p.st.ContextCancel()
// If we don't have a final status from the handshake finished function, stall for time
pst, _ := retry.DoWithData(
func() (protocol.Status, error) {
if p.st.FinalStatus == protocol.StatusUnknown {
return p.st.FinalStatus, errStall
}
return p.st.FinalStatus, nil
},
retry.Context(p.st.Context),
retry.Delay(10*time.Microsecond),
retry.DelayType(retry.BackOffDelay),
retry.MaxDelay(100*time.Millisecond),
retry.Attempts(0),
)
ctx.EndInnerProtocol(pst)
return nil
}
return p.startChunkedTransfer(p.st.Conn.OutboundData())
}
func (p *Payload) ModifyRADIUSResponse(r *radius.Packet, q *radius.Packet) error {
if r.Code != radius.CodeAccessAccept {
return nil
}
if p.st == nil || !p.st.HandshakeDone {
return nil
}
log.Debug("TLS: Adding MPPE Keys")
// TLS overrides other protocols' MPPE keys
if len(microsoft.MSMPPERecvKey_Get(r, q)) > 0 {
microsoft.MSMPPERecvKey_Del(r)
}
if len(microsoft.MSMPPESendKey_Get(r, q)) > 0 {
microsoft.MSMPPESendKey_Del(r)
}
microsoft.MSMPPERecvKey_Set(r, p.st.MPPEKey[:32])
microsoft.MSMPPESendKey_Set(r, p.st.MPPEKey[64:64+32])
return nil
}
func (p *Payload) tlsInit(ctx protocol.Context) {
ctx.Log().Debug("TLS: no TLS connection in state yet, starting connection")
p.st.Context, p.st.ContextCancel = context.WithTimeout(context.Background(), staleConnectionTimeout*time.Second)
p.st.Conn = NewBuffConn(p.Data, p.st.Context)
cfg := ctx.ProtocolSettings().(TLSConfig).TLSConfig().Clone()
if klp, ok := os.LookupEnv("SSLKEYLOGFILE"); ok {
kl, err := os.OpenFile(klp, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
panic(err)
}
cfg.KeyLogWriter = kl
}
cfg.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) {
ctx.Log().Debugf("TLS: ClientHello: %+v\n", chi)
p.st.ClientHello = chi
return nil, nil
}
p.st.TLS = tls.Server(p.st.Conn, cfg)
p.st.TLS.SetDeadline(time.Now().Add(staleConnectionTimeout * time.Second))
go func() {
err := p.st.TLS.HandshakeContext(p.st.Context)
if err != nil {
ctx.Log().WithError(err).Debug("TLS: Handshake error")
p.st.FinalStatus = protocol.StatusError
ctx.EndInnerProtocol(protocol.StatusError)
return
}
ctx.Log().Debug("TLS: handshake done")
p.tlsHandshakeFinished(ctx)
}()
}
func (p *Payload) tlsHandshakeFinished(ctx protocol.Context) {
cs := p.st.TLS.ConnectionState()
label := "client EAP encryption"
var context []byte
switch cs.Version {
case tls.VersionTLS10:
ctx.Log().Debugf("TLS: Version %d (1.0)", cs.Version)
case tls.VersionTLS11:
ctx.Log().Debugf("TLS: Version %d (1.1)", cs.Version)
case tls.VersionTLS12:
ctx.Log().Debugf("TLS: Version %d (1.2)", cs.Version)
case tls.VersionTLS13:
ctx.Log().Debugf("TLS: Version %d (1.3)", cs.Version)
label = "EXPORTER_EAP_TLS_Key_Material"
context = []byte{byte(TypeTLS)}
}
ksm, err := cs.ExportKeyingMaterial(label, context, 64+64)
ctx.Log().Debugf("TLS: ksm % x %v", ksm, err)
p.st.MPPEKey = ksm
p.st.HandshakeDone = true
if p.Inner == nil {
p.st.FinalStatus = ctx.ProtocolSettings().(Settings).HandshakeSuccessful(ctx, cs.PeerCertificates)
}
}
func (p *Payload) startChunkedTransfer(data []byte) *Payload {
if len(data) > maxChunkSize {
log.WithField("length", len(data)).Debug("TLS: Data needs to be chunked")
p.st.RemainingChunks = append(p.st.RemainingChunks, slices.Collect(slices.Chunk(data, maxChunkSize))...)
p.st.TotalPayloadSize = len(data)
return p.sendNextChunk()
}
log.WithField("length", len(data)).Debug("TLS: Sending data un-chunked")
p.st.Conn.writer.Reset()
return &Payload{
Flags: FlagLengthIncluded,
Length: uint32(len(data)),
Data: data,
}
}
func (p *Payload) sendNextChunk() *Payload {
nextChunk := p.st.RemainingChunks[0]
log.WithField("raw", debug.FormatBytes(nextChunk)).Debug("TLS: Sending next chunk")
p.st.RemainingChunks = p.st.RemainingChunks[1:]
flags := FlagLengthIncluded
if p.st.HasMore() {
log.WithField("chunks", len(p.st.RemainingChunks)).Debug("TLS: More chunks left")
flags += FlagMoreFragments
} else {
// Last chunk, reset the connection buffers and pending payload size
defer func() {
log.Debug("TLS: Sent last chunk")
p.st.Conn.writer.Reset()
p.st.TotalPayloadSize = 0
}()
}
log.WithField("length", p.st.TotalPayloadSize).Debug("TLS: Total payload size")
return &Payload{
Flags: flags,
Length: uint32(p.st.TotalPayloadSize),
Data: nextChunk,
}
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<TLS Packet HandshakeDone=%t, FinalStatus=%d, ClientHello=%v>",
p.st.HandshakeDone,
p.st.FinalStatus,
p.st.ClientHello,
)
}

View File

@ -0,0 +1,21 @@
package tls
import (
"crypto/tls"
"crypto/x509"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
type TLSConfig interface {
TLSConfig() *tls.Config
}
type Settings struct {
Config *tls.Config
HandshakeSuccessful func(ctx protocol.Context, certs []*x509.Certificate) protocol.Status
}
func (s Settings) TLSConfig() *tls.Config {
return s.Config
}

View File

@ -0,0 +1,32 @@
package tls
import (
"context"
"crypto/tls"
"goauthentik.io/internal/outpost/radius/eap/protocol"
)
type State struct {
RemainingChunks [][]byte
HandshakeDone bool
FinalStatus protocol.Status
ClientHello *tls.ClientHelloInfo
MPPEKey []byte
TotalPayloadSize int
TLS *tls.Conn
Conn *BuffConn
Context context.Context
ContextCancel context.CancelFunc
}
func NewState(c protocol.Context) interface{} {
c.Log().Debug("TLS: new state")
return &State{
RemainingChunks: make([][]byte, 0),
}
}
func (s State) HasMore() bool {
return len(s.RemainingChunks) > 0
}

View File

@ -1,17 +1,44 @@
package radius
import (
"context"
ttls "crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"net/url"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/radius/eap"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/gtc"
"goauthentik.io/internal/outpost/radius/eap/protocol/identity"
"goauthentik.io/internal/outpost/radius/eap/protocol/legacy_nak"
"goauthentik.io/internal/outpost/radius/eap/protocol/mschapv2"
"goauthentik.io/internal/outpost/radius/eap/protocol/peap"
"goauthentik.io/internal/outpost/radius/eap/protocol/tls"
"goauthentik.io/internal/outpost/radius/metrics"
"goauthentik.io/internal/utils"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2869"
)
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
eap := rfc2869.EAPMessage_Get(r.Packet)
if len(eap) > 0 {
rs.log.Trace("EAP request")
rs.Handle_AccessRequest_EAP(w, r)
} else {
rs.log.Trace("PAP request")
rs.Handle_AccessRequest_PAP(w, r)
}
}
func (rs *RadiusServer) Handle_AccessRequest_PAP(w radius.ResponseWriter, r *RadiusRequest) {
username := rfc2865.UserName_GetString(r.Packet)
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
@ -87,3 +114,164 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
res.Add(attr.Type, attr.Attribute)
}
}
func (rs *RadiusServer) Handle_AccessRequest_EAP(w radius.ResponseWriter, r *RadiusRequest) {
er := rfc2869.EAPMessage_Get(r.Packet)
ep, err := eap.Decode(r.pi, er)
if err != nil {
rs.log.WithError(err).Warning("failed to parse EAP packet")
return
}
ep.HandleRadiusPacket(w, r.Request)
}
func (pi *ProviderInstance) GetEAPState(key string) *protocol.State {
return pi.eapState[key]
}
func (pi *ProviderInstance) SetEAPState(key string, state *protocol.State) {
pi.eapState[key] = state
}
func (pi *ProviderInstance) GetEAPSettings() protocol.Settings {
protocols := []protocol.ProtocolConstructor{
identity.Protocol,
legacy_nak.Protocol,
}
certId := pi.certId
if certId == "" {
return protocol.Settings{
Protocols: protocols,
}
}
cert := pi.s.cryptoStore.Get(certId)
if cert == nil {
return protocol.Settings{
Protocols: protocols,
}
}
return protocol.Settings{
Protocols: append(protocols, tls.Protocol, peap.Protocol),
ProtocolPriority: []protocol.Type{tls.TypeTLS, peap.TypePEAP},
ProtocolSettings: map[protocol.Type]interface{}{
tls.TypeTLS: tls.Settings{
Config: &ttls.Config{
Certificates: []ttls.Certificate{*cert},
ClientAuth: ttls.RequireAnyClientCert,
},
HandshakeSuccessful: func(ctx protocol.Context, certs []*x509.Certificate) protocol.Status {
ctx.Log().Debug("Starting authn flow")
pem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certs[0].Raw,
})
fe := flow.NewFlowExecutor(context.Background(), pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
"client": utils.GetIP(ctx.Packet().RemoteAddr),
})
fe.DelegateClientIP(utils.GetIP(ctx.Packet().RemoteAddr))
fe.Params.Add("goauthentik.io/outpost/radius", "true")
fe.AddHeader("X-Authentik-Outpost-Certificate", url.QueryEscape(string(pem)))
passed, err := fe.Execute()
if err != nil {
ctx.Log().WithError(err).Warning("failed to execute flow")
return protocol.StatusError
}
ctx.Log().WithField("passed", passed).Debug("Finished flow")
if passed {
return protocol.StatusSuccess
} else {
return protocol.StatusError
}
},
},
peap.TypePEAP: peap.Settings{
Config: &ttls.Config{
Certificates: []ttls.Certificate{*cert},
},
InnerProtocols: protocol.Settings{
Protocols: append(protocols, gtc.Protocol, mschapv2.Protocol),
ProtocolPriority: []protocol.Type{gtc.TypeGTC, mschapv2.TypeMSCHAPv2},
ProtocolSettings: map[protocol.Type]interface{}{
mschapv2.TypeMSCHAPv2: mschapv2.Settings{
AuthenticateRequest: mschapv2.DebugStaticCredentials(
[]byte("foo"), []byte("bar"),
),
},
gtc.TypeGTC: gtc.Settings{
ChallengeHandler: func(ctx protocol.Context) (gtc.GetChallenge, gtc.ValidateResponse) {
fe := flow.NewFlowExecutor(context.Background(), pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
"client": utils.GetIP(ctx.Packet().RemoteAddr),
})
fe.DelegateClientIP(utils.GetIP(ctx.Packet().RemoteAddr))
fe.Params.Add("goauthentik.io/outpost/radius", "true")
var ch []byte = nil
var ans []byte = nil
fe.InteractiveSolver = func(ct *api.ChallengeTypes, afesr api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) {
comp := ct.GetActualInstance().(flow.ChallengeCommon).GetComponent()
ch = []byte(comp)
for {
if ans == nil {
continue
}
break
}
switch comp {
case string(flow.StageIdentification):
r := api.NewIdentificationChallengeResponseRequest(string(ans))
return api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(r), nil
case string(flow.StagePassword):
r := api.NewPasswordChallengeResponseRequest(string(ans))
return api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(r), nil
}
panic(comp)
}
passed := false
done := false
go func() {
var err error
passed, err = fe.Execute()
done = true
if err != nil {
ctx.Log().WithError(err).Warning("failed to execute flow")
// return protocol.StatusError
}
// ctx.Log().WithField("passed", passed).Debug("Finished flow")
// if passed {
// return protocol.StatusSuccess
// } else {
// return protocol.StatusError
// }
}()
return func() []byte {
if done {
status := protocol.StatusError
if passed {
status = protocol.StatusSuccess
}
ctx.EndInnerProtocol(status)
}
for {
if ch == nil {
continue
}
defer func() {
ch = nil
}()
return ch
}
}, func(answer []byte) {
ans = answer
}
},
},
},
},
},
},
}
}

View File

@ -3,6 +3,7 @@ package radius
import (
"crypto/sha512"
"encoding/hex"
"net"
"time"
"github.com/getsentry/sentry-go"
@ -35,12 +36,32 @@ func (r *RadiusRequest) ID() string {
return r.id
}
type LogWriter struct {
w radius.ResponseWriter
l *log.Entry
}
func (lw LogWriter) Write(packet *radius.Packet) error {
lw.l.WithField("code", packet.Code.String()).Info("Radius Response")
return lw.w.Write(packet)
}
func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) {
span := sentry.StartSpan(r.Context(), "authentik.providers.radius.connect",
sentry.WithTransactionName("authentik.providers.radius.connect"))
rid := uuid.New().String()
span.SetTag("request_uid", rid)
rl := rs.log.WithField("code", r.Code.String()).WithField("request", rid)
host, _, err := net.SplitHostPort(r.RemoteAddr.String())
if err != nil {
rs.log.WithError(err).Warning("Failed to get remote IP")
return
}
rl := rs.log.WithFields(log.Fields{
"code": r.Code.String(),
"request": rid,
"ip": host,
"id": r.Identifier,
})
selectedApp := ""
defer func() {
span.Finish()
@ -58,6 +79,7 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
}
rl.Info("Radius Request")
ww := LogWriter{w, rl}
// Lookup provider by shared secret
var pi *ProviderInstance
@ -72,12 +94,12 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
hs := sha512.Sum512([]byte(r.Secret))
bs := hex.EncodeToString(hs[:])
nr.Log().WithField("hashed_secret", bs).Warning("No provider found")
_ = w.Write(r.Response(radius.CodeAccessReject))
_ = ww.Write(r.Response(radius.CodeAccessReject))
return
}
nr.pi = pi
if nr.Code == radius.CodeAccessRequest {
rs.Handle_AccessRequest(w, nr)
rs.Handle_AccessRequest(ww, nr)
}
}

View File

@ -9,6 +9,7 @@ import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/metrics"
"layeh.com/radius"
@ -22,23 +23,27 @@ type ProviderInstance struct {
appSlug string
flowSlug string
providerId int32
certId string
s *RadiusServer
log *log.Entry
eapState map[string]*protocol.State
}
type RadiusServer struct {
s radius.PacketServer
log *log.Entry
ac *ak.APIController
s radius.PacketServer
log *log.Entry
ac *ak.APIController
cryptoStore *ak.CryptoStore
providers []*ProviderInstance
providers map[int32]*ProviderInstance
}
func NewServer(ac *ak.APIController) ak.Outpost {
rs := &RadiusServer{
log: log.WithField("logger", "authentik.outpost.radius"),
ac: ac,
providers: []*ProviderInstance{},
log: log.WithField("logger", "authentik.outpost.radius"),
ac: ac,
providers: map[int32]*ProviderInstance{},
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi),
}
rs.s = radius.PacketServer{
Handler: rs,
@ -85,7 +90,7 @@ func (rs *RadiusServer) RADIUSSecret(ctx context.Context, remoteAddr net.Addr) (
return bi < bj
})
candidate := matchedPrefixes[0]
rs.log.WithField("ip", ip.String()).WithField("cidr", candidate.c.String()).Debug("Matched CIDR")
rs.log.WithField("ip", ip.String()).WithField("cidr", candidate.c.String()).WithField("instance", candidate.p.appSlug).Debug("Matched CIDR")
return candidate.p.SharedSecret, nil
}
@ -98,7 +103,8 @@ func (rs *RadiusServer) Start() error {
}()
go func() {
defer wg.Done()
err := rs.StartRadiusServer()
rs.log.WithField("listen", rs.s.Addr).Info("Starting radius server")
err := rs.s.ListenAndServe()
if err != nil {
panic(err)
}

View File

@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2025.6.2
Default: 2025.6.3
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

Binary file not shown.

View File

@ -11,18 +11,18 @@
# Nicola Mersi, 2024
# tmassimi, 2024
# Marc Schmitt, 2024
# albanobattistella <albanobattistella@gmail.com>, 2024
# Matteo Piccina <altermatte@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# albanobattistella <albanobattistella@gmail.com>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -116,7 +116,7 @@ msgstr "Certificato Web utilizzato dal server Web authentik Core."
#: authentik/brands/models.py
msgid "Certificates used for client authentication."
msgstr ""
msgstr "Certificati utilizzati per l'autenticazione del client."
#: authentik/brands/models.py
msgid "Brand"
@ -130,10 +130,6 @@ msgstr "Brands"
msgid "User does not have access to application."
msgstr "L'utente non ha accesso all'applicazione."
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Descrizione extra non disponibile"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "Impossibile impostare il gruppo come padre di se stesso."
@ -294,15 +290,15 @@ msgid ""
msgstr ""
"Collegamento a un utente con indirizzo email identico. Può avere "
"implicazioni sulla sicurezza quando una fonte non convalida gli indirizzi "
"e-mail."
"email."
#: authentik/core/models.py
msgid ""
"Use the user's email address, but deny enrollment when the email address "
"already exists."
msgstr ""
"Usa l'indirizzo e-mail dell'utente, ma nega l'iscrizione quando l'indirizzo "
"e-mail esiste già."
"Usa l'indirizzo email dell'utente, ma nega l'iscrizione quando l'indirizzo "
"email esiste già."
#: authentik/core/models.py
msgid ""
@ -682,26 +678,29 @@ msgid ""
"option has a higher priority than the `client_certificate` option on "
"`Brand`."
msgstr ""
"Configura le autorità di certificazione per convalidare il certificato. "
"Questa opzione ha una priorità maggiore rispetto all'opzione "
"`client_certificate` su `Brand`."
#: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stage"
msgstr ""
msgstr "Fase di TLS reciproca"
#: authentik/enterprise/stages/mtls/models.py
msgid "Mutual TLS Stages"
msgstr ""
msgstr "Fasi di TLS reciproche"
#: authentik/enterprise/stages/mtls/models.py
msgid "Permissions to pass Certificates for outposts."
msgstr ""
msgstr " Permessi di trasmissione dei Certificati per gli avamposti."
#: authentik/enterprise/stages/mtls/stage.py
msgid "Certificate required but no certificate was given."
msgstr ""
msgstr " Il certificato è stato richiesto ma non è stato consegnato."
#: authentik/enterprise/stages/mtls/stage.py
msgid "No user found for certificate."
msgstr ""
msgstr "Nessun utente trovato per il certificato."
#: authentik/enterprise/stages/source/models.py
msgid ""
@ -834,6 +833,14 @@ msgstr ""
"Definisci a quale gruppo di utenti deve essere inviata e mostrata questa "
"notifica. Se lasciato vuoto, la notifica non verrà inviata."
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr ""
"Se abilitata, la notifica verrà inviata all'utente che ha attivato l'evento."
" Se destination_group è configurato, la notifica verrà inviata a entrambi."
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "Regola di notifica"
@ -1050,16 +1057,16 @@ msgstr "Avvio della sincronizzazione completa del provider"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing users"
msgstr ""
msgstr "Sincronizzazione degli utenti"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing groups"
msgstr ""
msgstr "Sincronizzazione dei gruppi"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
msgid "Syncing page {page} of groups"
msgstr "Sincronizzando pagina {page} dei gruppi"
msgid "Syncing page {page} of {object_type}"
msgstr "Sincronizzazione della pagina {page} di {object_type}"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Dropping mutating request due to dry run"
@ -2461,6 +2468,10 @@ msgstr "Gruppo di aggiunta DN"
msgid "Consider Objects matching this filter to be Users."
msgstr "Considerare gli oggetti corrispondenti a questo filtro come Utenti."
#: authentik/sources/ldap/models.py
msgid "Attribute which matches the value of `group_membership_field`."
msgstr "Attributo che corrisponde al valore di `group_membership_field`."
#: authentik/sources/ldap/models.py
msgid "Field which contains members of a group."
msgstr "Campo che contiene i membri di un gruppo."
@ -2502,6 +2513,8 @@ msgid ""
"Delete authentik users and groups which were previously supplied by this "
"source, but are now missing from it."
msgstr ""
"Elimina gli utenti e i gruppi authentik precedentemente forniti da questa "
"fonte, ma che ora mancano."
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@ -2523,6 +2536,8 @@ msgstr "Mappature delle proprietà della sorgente LDAP"
msgid ""
"Unique ID used while checking if this object still exists in the directory."
msgstr ""
"ID univoco utilizzato per verificare se questo oggetto esiste ancora nella "
"directory."
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
@ -2920,7 +2935,7 @@ msgstr "Connessioni sorgente SAML di gruppo"
#: authentik/sources/saml/views.py
#, python-brace-format
msgid "Continue to {source_name}"
msgstr ""
msgstr "Continua su {source_name}"
#: authentik/sources/scim/models.py
msgid "SCIM Source"
@ -2988,8 +3003,8 @@ msgstr "Fasi di configurazione dell'autenticatore email"
#: authentik/stages/email/stage.py
msgid "Exception occurred while rendering E-mail template"
msgstr ""
"Eccezione verificatasi durante la visualizzazione del modello di posta "
"elettronica"
"Si è verificata un'eccezione durante la visualizzazione del modello di posta"
" elettronica"
#: authentik/stages/authenticator_email/models.py
msgid "Email Device"
@ -3028,7 +3043,7 @@ msgid ""
" "
msgstr ""
"\n"
" Codice MFA via e-mail.\n"
" Codice MFA via email.\n"
" "
#: authentik/stages/authenticator_email/templates/email/email_otp.html
@ -3054,7 +3069,7 @@ msgid ""
"Email MFA code\n"
msgstr ""
"\n"
"Codice e-mail MFA\n"
"Codice email MFA\n"
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
#, python-format
@ -3321,7 +3336,7 @@ msgstr "Consensi utente"
#: authentik/stages/consent/stage.py
msgid "Invalid consent token, re-showing prompt"
msgstr ""
msgstr "Token di consenso non valido, viene nuovamente visualizzato il prompt"
#: authentik/stages/deny/models.py
msgid "Deny Stage"
@ -3341,11 +3356,11 @@ msgstr "Fasi fittizie"
#: authentik/stages/email/flow.py
msgid "Continue to confirm this email address."
msgstr ""
msgstr "Continua per confermare questo indirizzo email."
#: authentik/stages/email/flow.py
msgid "Link was already used, please request a new link."
msgstr ""
msgstr "Il collegamento è già stato utilizzato. Richiedine uno nuovo."
#: authentik/stages/email/models.py
msgid "Password Reset"
@ -3365,7 +3380,7 @@ msgstr "Fase email"
#: authentik/stages/email/models.py
msgid "Email Stages"
msgstr "Fasi Email"
msgstr "Fasi email"
#: authentik/stages/email/stage.py
msgid "Successfully verified Email."
@ -3467,7 +3482,7 @@ msgid ""
" "
msgstr ""
"\n"
" Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
" Se non hai richiesto una modifica della password, ignora questa email. Il link sopra è valido per %(expires)s.\n"
" "
#: authentik/stages/email/templates/email/password_reset.txt
@ -3485,11 +3500,11 @@ msgid ""
"If you did not request a password change, please ignore this email. The link above is valid for %(expires)s.\n"
msgstr ""
"\n"
"Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
"Se non hai richiesto una modifica della password, ignora questa email. Il link sopra è valido per %(expires)s.\n"
#: authentik/stages/email/templates/email/setup.html
msgid "authentik Test-Email"
msgstr "e-mail di prova di authentik"
msgstr "email di prova di authentik"
#: authentik/stages/email/templates/email/setup.html
msgid ""
@ -3498,7 +3513,7 @@ msgid ""
" "
msgstr ""
"\n"
" Questa è un'e-mail di prova per informarti che hai configurato correttamente le e-mail di authentik.\n"
" Questa è un'email di prova per informarti che hai configurato correttamente le email di authentik.\n"
" "
#: authentik/stages/email/templates/email/setup.txt
@ -3507,7 +3522,7 @@ msgid ""
"This is a test email to inform you, that you've successfully configured authentik emails.\n"
msgstr ""
"\n"
"Questa è un'e-mail di prova per informarti che hai configurato correttamente le e-mail di authentik.\n"
"Questa è un'email di prova per informarti che hai configurato correttamente le email di authentik.\n"
#: authentik/stages/identification/api.py
msgid "When no user fields are selected, at least one source must be selected"
@ -3710,7 +3725,7 @@ msgstr ""
#: authentik/stages/prompt/models.py
msgid "Email: Text field with Email type."
msgstr "E-mail: Campo di testo con il tipo di e-mail."
msgstr "Email: Campo di testo con il tipo di email."
#: authentik/stages/prompt/models.py
msgid ""
@ -3865,10 +3880,6 @@ msgstr "Fasi di accesso utente"
msgid "No Pending user to login."
msgstr "Nessun utente in attesa di accesso."
#: authentik/stages/user_login/stage.py
msgid "Successfully logged in!"
msgstr "Accesso effettuato!"
#: authentik/stages/user_logout/models.py
msgid "User Logout Stage"
msgstr "Fase di disconnessione dell'utente"

Binary file not shown.

View File

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -118,10 +118,6 @@ msgstr "品牌"
msgid "User does not have access to application."
msgstr "用户没有访问此应用程序的权限。"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "额外描述不可用"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "无法设置组自身为父级。"
@ -775,6 +771,12 @@ msgid ""
"If left empty, Notification won't ben sent."
msgstr "定义此通知应该发送到哪些用户组。如果留空,则不会发送通知。"
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr "启用时,通知会被发送到触发事件的用户。当配置了 destination_group 时,通知也会同时发送到对应组。"
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "通知规则"

Binary file not shown.

View File

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -117,10 +117,6 @@ msgstr "品牌"
msgid "User does not have access to application."
msgstr "用户没有访问此应用程序的权限。"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "额外描述不可用"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "无法设置组自身为父级。"
@ -774,6 +770,12 @@ msgid ""
"If left empty, Notification won't ben sent."
msgstr "定义此通知应该发送到哪些用户组。如果留空,则不会发送通知。"
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr "启用时,通知会被发送到触发事件的用户。当配置了 destination_group 时,通知也会同时发送到对应组。"
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "通知规则"

4
notes.md Normal file
View File

@ -0,0 +1,4 @@
eapol_test -s foo -a 192.168.68.1 -c config
sudo tcpdump -i bridge100 port 1812 -w eap.pcap

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@goauthentik/authentik",
"version": "2025.6.2",
"version": "2025.6.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/authentik",
"version": "2025.6.2",
"version": "2025.6.3",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3",

View File

@ -1,6 +1,6 @@
{
"name": "@goauthentik/authentik",
"version": "2025.6.2",
"version": "2025.6.3",
"private": true,
"type": "module",
"devDependencies": {

View File

@ -2,11 +2,8 @@
* @file Prettier configuration for authentik.
*
* @import { Config as PrettierConfig } from "prettier";
*/
import { importsPlugin } from "./imports.js";
/**
* @import { PluginConfig as SortPluginConfig } from "@trivago/prettier-plugin-sort-imports";
*
* @typedef {object} PackageJSONPluginConfig
* @property {string[]} [packageSortOrder] Custom ordering array.
*/
@ -14,7 +11,7 @@ import { importsPlugin } from "./imports.js";
/**
* authentik Prettier configuration.
*
* @type {PrettierConfig & PackageJSONPluginConfig}
* @type {PrettierConfig & SortPluginConfig & PackageJSONPluginConfig}
* @internal
*/
export const AuthentikPrettierConfig = {
@ -37,8 +34,32 @@ export const AuthentikPrettierConfig = {
plugins: [
// ---
"prettier-plugin-packagejson",
importsPlugin(),
"@trivago/prettier-plugin-sort-imports",
],
importOrder: [
// ---
"^(@goauthentik/|#)common.+",
"^(@goauthentik/|#)elements.+",
"^(@goauthentik/|#)components.+",
"^(@goauthentik/|#)user.+",
"^(@goauthentik/|#)admin.+",
"^(@goauthentik/|#)flow.+",
"^#.+",
"^@goauthentik.+",
"<THIRD_PARTY_MODULES>",
"^(@?)lit(.*)$",
"\\.css$",
"^@goauthentik/api$",
"^[./]",
],
importOrderSideEffects: false,
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
overrides: [
{
files: "schemas/**/*.json",

View File

@ -1,172 +0,0 @@
import { createRequire } from "node:module";
import { formatSourceFromFile } from "format-imports";
import { parsers as babelParsers } from "prettier/plugins/babel";
/**
* @file Prettier import plugin.
*
* @import { Plugin, ParserOptions } from "prettier";
*/
import { parsers as typescriptParsers } from "prettier/plugins/typescript";
const require = createRequire(process.cwd() + "/");
/**
* @param {string} name
* @returns {string | null}
*/
function resolveModule(name) {
try {
return require.resolve(name);
} catch (error) {
return null;
}
}
const webSubmodules = [
// ---
"common",
"elements",
"components",
"user",
"admin",
"flow",
];
/**
* Ensure that every import without an extension adds one.
* @param {string} input
* @returns {string}
*/
function normalizeExtensions(input) {
return input.replace(/(?:import|from)\s*["']((?:\.\.?\/).*?)(?<!\.\w+)["']/gm, (line, path) => {
return line.replace(path, `${path}.js`);
});
}
/**
* @param {string} filepath
* @param {string} input
* @returns {string}
*/
function normalizeImports(filepath, input) {
let output = input;
// Replace all TypeScript imports with the paths resolved by Node/Browser import maps.
for (const submodule of webSubmodules) {
const legacyPattern = new RegExp(
[
// ---
`(?:import|from)`,
`\\\(?\\n?\\s*`,
`"(?<suffix>@goauthentik\/${submodule}\/)`,
`(?<path>[^"'.]+)`,
`(?:\.[^"']+)?["']`,
`\\n?\\s*\\\)?;`,
].join(""),
"gm",
);
output = output.replace(
legacyPattern,
/**
* @param {string} line
* @param {string} suffix
* @param {string} path
*/
(line, suffix, path) => {
const exported = `@goauthentik/web/${submodule}/${path}`;
let imported = `#${submodule}/${path}`;
let module = resolveModule(`${exported}.ts`);
if (!module) {
module = resolveModule(`${exported}/index.ts`);
imported += "/index";
}
if (imported.endsWith(".css")) {
imported += ".js";
}
if (!module) {
console.warn(`\nCannot resolve module ${exported} from ${filepath}`, {
line,
path,
exported,
imported,
module,
});
process.exit(1);
}
return (
line
// ---
.replace(suffix + path, imported)
.replace(`${imported}.js`, imported)
);
},
);
}
return output;
}
/**
* @returns {Plugin}
*/
export function importsPlugin({
useLegacyCleanup = process.env.AK_FIX_LEGACY_IMPORTS === "true",
} = {}) {
/**
* @param {string} input
* @param {ParserOptions} options
*/
const preprocess = (input, { filepath, printWidth }) => {
let output = input;
if (useLegacyCleanup) {
output = normalizeExtensions(input);
output = normalizeImports(filepath, output);
}
const value = formatSourceFromFile.sync(output, filepath, {
nodeProtocol: "always",
maxLineLength: printWidth,
wrappingStyle: "prettier",
groupRules: [
"^node:",
...webSubmodules.map((submodule) => `^(@goauthentik/|#)${submodule}.+`),
"^#.+",
"^@goauthentik.+",
{}, // Other imports.
"^(@?)lit(.*)$",
"\\.css$",
"^@goauthentik/api$",
"^[./]",
],
});
return value || input;
};
return {
parsers: {
typescript: {
...typescriptParsers.typescript,
preprocess,
},
babel: {
...babelParsers.babel,
preprocess,
},
},
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.0.0",
"version": "2.0.1",
"description": "authentik's Prettier config",
"license": "MIT",
"scripts": {
@ -10,19 +10,19 @@
},
"type": "module",
"exports": "./index.js",
"dependencies": {
"format-imports": "^4.0.7"
},
"devDependencies": {
"@goauthentik/tsconfig": "^1.0.1",
"@types/node": "^24.0.4",
"prettier": "^3.6.1",
"prettier-plugin-packagejson": "^2.5.16",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.15",
"typescript": "^5.8.3"
},
"peerDependencies": {
"prettier": "^3.6.1",
"prettier-plugin-packagejson": "^2.5.16"
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.15"
},
"engines": {
"node": ">=22"

View File

@ -1,6 +1,6 @@
[project]
name = "authentik"
version = "2025.6.2"
version = "2025.6.3"
description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.13.*"
@ -43,7 +43,7 @@ dependencies = [
"jwcrypto==1.5.6",
"kubernetes==33.1.0",
"ldap3==2.9.1",
"lxml==5.4.0",
"lxml==6.0.0",
"msgraph-sdk==1.35.0",
"opencontainers==0.0.14",
"packaging==25.0",
@ -57,7 +57,7 @@ dependencies = [
"pyyaml==6.0.2",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",
"sentry-sdk==2.31.0",
"sentry-sdk==2.32.0",
"service-identity==24.2.0",
"setproctitle==1.3.6",
"structlog==25.4.0",
@ -67,7 +67,7 @@ dependencies = [
"ua-parser==1.0.1",
"unidecode==1.4.0",
"urllib3<3",
"uvicorn[standard]==0.34.3",
"uvicorn[standard]==0.35.0",
"watchdog==6.0.0",
"webauthn==2.6.0",
"wsproto==1.2.0",

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2025.6.2
version: 2025.6.3
description: Making authentication simple.
contact:
email: hello@goauthentik.io
@ -54849,6 +54849,10 @@ components:
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
certificate:
type: string
format: uuid
nullable: true
PatchedRedirectStageRequest:
type: object
description: RedirectStage Serializer
@ -57302,6 +57306,10 @@ components:
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
certificate:
type: string
format: uuid
nullable: true
required:
- application_slug
- auth_flow_slug
@ -57388,6 +57396,10 @@ components:
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
certificate:
type: string
format: uuid
nullable: true
required:
- assigned_application_name
- assigned_application_slug
@ -57512,6 +57524,10 @@ components:
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
certificate:
type: string
format: uuid
nullable: true
required:
- authorization_flow
- invalidation_flow

View File

@ -7,7 +7,7 @@ services:
network_mode: host
restart: always
mailpit:
image: docker.io/axllent/mailpit:v1.26.2
image: docker.io/axllent/mailpit:v1.27.0
ports:
- 1025:1025
- 8025:8025

53
uv.lock generated
View File

@ -165,7 +165,7 @@ wheels = [
[[package]]
name = "authentik"
version = "2025.6.2"
version = "2025.6.3"
source = { editable = "." }
dependencies = [
{ name = "argon2-cffi" },
@ -305,7 +305,7 @@ requires-dist = [
{ name = "jwcrypto", specifier = "==1.5.6" },
{ name = "kubernetes", specifier = "==33.1.0" },
{ name = "ldap3", specifier = "==2.9.1" },
{ name = "lxml", specifier = "==5.4.0" },
{ name = "lxml", specifier = "==6.0.0" },
{ name = "msgraph-sdk", specifier = "==1.35.0" },
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
{ name = "packaging", specifier = "==25.0" },
@ -319,7 +319,7 @@ requires-dist = [
{ name = "pyyaml", specifier = "==6.0.2" },
{ name = "requests-oauthlib", specifier = "==2.0.0" },
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
{ name = "sentry-sdk", specifier = "==2.31.0" },
{ name = "sentry-sdk", specifier = "==2.32.0" },
{ name = "service-identity", specifier = "==24.2.0" },
{ name = "setproctitle", specifier = "==1.3.6" },
{ name = "structlog", specifier = "==25.4.0" },
@ -329,7 +329,7 @@ requires-dist = [
{ name = "ua-parser", specifier = "==1.0.1" },
{ name = "unidecode", specifier = "==1.4.0" },
{ name = "urllib3", specifier = "<3" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.3" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.35.0" },
{ name = "watchdog", specifier = "==6.0.0" },
{ name = "webauthn", specifier = "==2.6.0" },
{ name = "wsproto", specifier = "==1.2.0" },
@ -1824,27 +1824,22 @@ wheels = [
[[package]]
name = "lxml"
version = "5.4.0"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
{ url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
{ url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
{ url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
{ url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
{ url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
{ url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
{ url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
{ url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
{ url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
{ url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
{ url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
{ url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
{ url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
{ url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
{ url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
{ url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" },
{ url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" },
{ url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" },
{ url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" },
{ url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" },
{ url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" },
{ url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" },
{ url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" },
{ url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" },
{ url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" },
{ url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" },
{ url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" },
]
[[package]]
@ -2961,15 +2956,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.31.0"
version = "2.32.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d0/45/c7ef7e12d8434fda8b61cdab432d8af64fb832480c93cdaf4bdcab7f5597/sentry_sdk-2.31.0.tar.gz", hash = "sha256:fed6d847f15105849cdf5dfdc64dcec356f936d41abb8c9d66adae45e60959ec", size = 334167, upload-time = "2025-06-24T16:36:26.066Z" }
sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload-time = "2025-06-27T08:10:02.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/a2/9b6d8cc59f03251c583b3fec9d2f075dc09c0f6e030e0e0a3b223c6e64b2/sentry_sdk-2.31.0-py2.py3-none-any.whl", hash = "sha256:e953f5ab083e6599bab255b75d6829b33b3ddf9931a27ca00b4ab0081287e84f", size = 355638, upload-time = "2025-06-24T16:36:24.306Z" },
{ url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload-time = "2025-06-27T08:10:01.424Z" },
]
[[package]]
@ -3322,15 +3317,15 @@ socks = [
[[package]]
name = "uvicorn"
version = "0.34.3"
version = "0.35.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" },
]
[package.optional-dependencies]

View File

@ -4,6 +4,7 @@
* @import { InlineConfig, Plugin } from "vite";
*/
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
const CSSImportPattern = /import [\w$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
@ -60,7 +61,7 @@ const config = {
*/
const overrides = {
define: createBundleDefinitions(),
plugins: [inlineCSSPlugin, postcssLit()],
plugins: [inlineCSSPlugin, postcssLit(), tsconfigPaths()],
};
return mergeConfig(config, overrides);

View File

@ -4,7 +4,6 @@
* @import { ThemeVarsPartial } from "storybook/internal/theming";
*/
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
import { addons } from "@storybook/manager-api";
import { create } from "@storybook/theming/create";

View File

@ -10,11 +10,10 @@
* PluginBuild
* } from "esbuild"
*/
import { MonoRepoRoot } from "@goauthentik/core/paths/node";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { MonoRepoRoot } from "@goauthentik/core/paths/node";
/**
* @typedef {Omit<OnLoadArgs, 'pluginData'> & LoadDataFields} LoadData Data passed to `onload`.
*

View File

@ -1,5 +1,4 @@
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
import tseslint from "typescript-eslint";
// @ts-check

46
web/package-lock.json generated
View File

@ -128,6 +128,7 @@
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.0",
"vite-plugin-lit-css": "^2.0.0",
"vite-tsconfig-paths": "^5.0.1",
"wireit": "^0.14.12"
},
"engines": {
@ -16313,6 +16314,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -26423,6 +26430,26 @@
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w=="
},
"node_modules/tsconfck": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.3.tgz",
"integrity": "sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==",
"dev": true,
"bin": {
"tsconfck": "bin/tsconfck.js"
},
"engines": {
"node": "^18 || >=20"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -27745,6 +27772,25 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite-tsconfig-paths": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz",
"integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==",
"dev": true,
"dependencies": {
"debug": "^4.1.1",
"globrex": "^0.1.2",
"tsconfck": "^3.0.3"
},
"peerDependencies": {
"vite": "*"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
}
},
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",

View File

@ -148,7 +148,7 @@
"@goauthentik/core": "^1.0.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.0.5",
"@goauthentik/eslint-config": "^1.0.5",
"@goauthentik/prettier-config": "^3.0.0",
"@goauthentik/prettier-config": "^1.0.5",
"@goauthentik/tsconfig": "^1.0.4",
"@hcaptcha/types": "^1.0.4",
"@lit/localize-tools": "^0.8.0",
@ -199,6 +199,7 @@
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.0",
"vite-plugin-lit-css": "^2.0.0",
"vite-tsconfig-paths": "^5.0.1",
"wireit": "^0.14.12"
},
"optionalDependencies": {

View File

@ -7,4 +7,6 @@
* @ignore
*/
export {};
export default {};

View File

@ -3,12 +3,9 @@
*
* @runtime node
*/
import { MonoRepoRoot } from "#paths/node";
import { execSync } from "node:child_process";
import { MonoRepoRoot } from "#paths/node";
// ts-import-sorter: disable
import PackageJSON from "../../../../package.json" with { type: "json" };
/**

View File

@ -1,11 +1,10 @@
/**
* @file Rollup configuration for the SFE package.
*/
import { resolve as resolvePath } from "node:path";
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import swc from "@rollup/plugin-swc";
import { resolve as resolvePath } from "node:path";
import copy from "rollup-plugin-copy";
export async function createConfig() {

View File

@ -1,4 +1,6 @@
import { fromByteArray } from "base64-js";
import "formdata-polyfill";
import $ from "jquery";
import "weakmap-polyfill";
import {
@ -14,9 +16,6 @@ import {
type RedirectChallenge,
} from "@goauthentik/api";
import { fromByteArray } from "base64-js";
import $ from "jquery";
interface GlobalAuthentik {
brand: {
branding_logo: string;

View File

@ -3,12 +3,10 @@
*
* @runtime node
*/
import { DistDirectoryName } from "#paths";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { DistDirectoryName } from "#paths";
const relativeDirname = dirname(fileURLToPath(import.meta.url));
//#region Base paths

View File

@ -9,15 +9,14 @@
* long spew of "this string is not translated" and replacing it with a
* summary of how many strings are missing with respect to the source locale.
*
* @import { ConfigFile } from "@lit/localize-tools/lib/types/config.js"
* @import { Stats } from "node:fs";
* @import { ConfigFile } from "@lit/localize-tools/lib/types/config"
* @import { Stats } from "fs";
*/
import { PackageRoot } from "#paths/node";
import { spawnSync } from "node:child_process";
import { readFileSync, statSync } from "node:fs";
import path from "node:path";
import { PackageRoot } from "#paths/node";
/**
* @type {ConfigFile}
*/

View File

@ -1,25 +1,21 @@
/// <reference types="../types/esbuild.js" />
/**
* @file ESBuild script for building the authentik web UI.
*
* @import { BuildOptions } from "esbuild";
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { mdxPlugin } from "#bundler/mdx-plugin/node";
import { createBundleDefinitions } from "#bundler/utils/node";
import { DistDirectory, EntryPoint, PackageRoot } from "#paths/node";
import { NodeEnvironment } from "@goauthentik/core/environment/node";
import { MonoRepoRoot, resolvePackage } from "@goauthentik/core/paths/node";
import { readBuildIdentifier } from "@goauthentik/core/version/node";
import { deepmerge } from "deepmerge-ts";
import esbuild from "esbuild";
import { copy } from "esbuild-plugin-copy";
import copy from "esbuild-plugin-copy";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
/// <reference types="../types/esbuild.js" />
import * as fs from "node:fs/promises";
import * as path from "node:path";
const logPrefix = "[Build]";

View File

@ -6,12 +6,9 @@
* @import { ProgramMessage } from "@lit/localize-tools/src/messages.js"
* @import { Locale } from "@lit/localize-tools/src/types/locale.js"
*/
import { PackageRoot } from "#paths/node";
import { readFileSync } from "node:fs";
import path from "node:path";
import { PackageRoot } from "#paths/node";
import pseudolocale from "pseudolocale";
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";

View File

@ -1,21 +1,19 @@
import "#elements/EmptyState";
import { DEFAULT_CONFIG } from "#common/api/config";
import { globalAK } from "#common/global";
import { ModalButton } from "#elements/buttons/ModalButton";
import { WithBrandConfig } from "#elements/mixins/branding";
import { WithLicenseSummary } from "#elements/mixins/license";
import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { globalAK } from "@goauthentik/common/global";
import "@goauthentik/elements/EmptyState";
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
import { msg } from "@lit/localize";
import { css, html, TemplateResult } from "lit";
import { TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthentik/api";
@customElement("ak-about-modal")
export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) {
static get styles() {

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