Compare commits

...

34 Commits

Author SHA1 Message Date
739acf50f4 providers/radius: add logout support
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-04-01 03:34:07 +02:00
ac1f3332dc web/admin: allow custom sorting for bound* tables (#9080)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-03-30 21:35:28 +01:00
2c64f72ebc web: move context controllers into reactive controller plugins (#8996)
* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* web: move context controllers into reactive controller plugins

While I was working on the Patternfly 5 thing, I found myself cleaning up the
way our context controllers are plugged into the Interfaces.  I realized a
couple of things that had bothered me before:

1. It does not matter where the context controller lives so long as the context
   controller has a references to the LitElement that hosts it.
   ReactiveControllers provide that reference.
2. ReactiveControllers are a perfect place to hide some of these details, so
   that they don't have to clutter up our Interface declaration.
3. The ReactiveController `hostConnected()/hostDisconnected()` lifecycle is a
   much better place to hook up our EVENT_REFRESH events to the contexts and
   controllers that care about them than some random place in the loader cycle.
4. It's much easier to detect and control when an external change to a
   context's state object, which is supposed to be a mirror of the context,
   changes outside the controller, by using the `hostUpdate()` method.  When the
   controller causes a state change, the states will be the same, allowing us to
   short out the potential infinite loop.

This commit also uses the symbol-as-property-name trick to guarantee the privacy
of some fields that should truly be private. They're unfindable and
inaddressible from the outside world. This is preferable to using the Private
Member syntax (the `#` prefix) because Babel, TypeScript, and ESBuild all use an
underlying registry of private names that "do not have good performance
characteristics if you create many instances of classes with private fields"
[ESBuild Caveats](https://esbuild.github.io/content-types/#javascript-caveats).
2024-03-29 11:59:17 -07:00
51a8670a13 web: maintenance: split tsconfig into “base” and “build” variants. (#9036)
* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* web: maintenance.  Split tsconfig into "base" and "build" variants.

This commit creates the now fairly standard split between the tsconfig "build" and "base"
variants.  This split is useful in defining build variants that have a default set of
rules (such as library use, language constraints, and specialized plug-in checks) but
can be varied in "extension" files.

The most common use for this is to allow for IDE-specific versions of tsconfig (which
know only to look for `tsconfig.json`) while enabling providing more comprehensive
variants to build and lint systems.

This commit is intended to enable this behavior so that different versions of Patternfly
can be included in a slow, evolutionary way that won't create too many incomprehensibly
huge reviews in the coming days.

A comparison of the produced configs, derived by `tsc --showConfig`, between this branch
and _main_ show no difference in the output of the complete tsconfig.json used by the
compiler.

---

It annoys me, a *lot*, that Doug Crockford didn't allow comments in JSON files,
and both the NPM folks and the TSC folks have been obstinate in not permitting
alternative formats for their configuration files. This makes it impossible to
comment some of the most important and complicated files in our system.

* Restarted the webui docs folder.  Docs should always live with the project.

* web: prettier has opinions.
2024-03-29 10:12:45 -07:00
b9f6cd9226 web: consistent style declarations internally (#9077)
* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* web: consistency pass

While investigating the viability of applying purgeCSS to Patternfly4, in order
to reduce the weight of our CSS, I found these four locations in our code (all
of them *my changes*, darnit), in which our usual `styles` declaration pattern
was inconsistent with our own standards. The LibraryPageImpl change would have
been too intrusive to make fully compliant. The objective here is to ensure that
our objects have *predictable* internal layouts for ease of future maintenance.
2024-03-29 10:12:18 -07:00
7010682122 providers/oauth2: fix interactive device flow (#9076)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-03-29 15:38:49 +01:00
0e82facfb4 website/docs: fix transports example (#9074)
Update transports.md

request.context['notification'].body is correct.

Signed-off-by: Mrs Feathers <echo@furryrefuge.com>
2024-03-29 14:47:42 +01:00
afdff95453 events: fix log_capture (#9075)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-03-29 14:44:14 +01:00
b11f12b1db web: bump the sentry group in /web with 2 updates (#9065)
Bumps the sentry group in /web with 2 updates: [@sentry/browser](https://github.com/getsentry/sentry-javascript) and @spotlightjs/spotlight.


Updates `@sentry/browser` from 7.108.0 to 7.109.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.109.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.108.0...7.109.0)

Updates `@spotlightjs/spotlight` from 1.2.15 to 1.2.16

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
- dependency-name: "@spotlightjs/spotlight"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: sentry
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:37:22 +01:00
4df906e32c core: bump goauthentik.io/api/v3 from 3.2024022.6 to 3.2024022.7 (#9064)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024022.6 to 3.2024022.7.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024022.6...v3.2024022.7)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:06:45 +01:00
fee7abed7c web: bump @codemirror/lang-python from 6.1.4 to 6.1.5 in /web (#9068)
Bumps [@codemirror/lang-python](https://github.com/codemirror/lang-python) from 6.1.4 to 6.1.5.
- [Changelog](https://github.com/codemirror/lang-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/lang-python/compare/6.1.4...6.1.5)

---
updated-dependencies:
- dependency-name: "@codemirror/lang-python"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:06:31 +01:00
d1a5d0dd7e web: bump the eslint group in /web with 1 update (#9066)
Bumps the eslint group in /web with 1 update: [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs).


Updates `eslint-plugin-sonarjs` from 0.24.0 to 0.25.0
- [Release notes](https://github.com/SonarSource/eslint-plugin-sonarjs/releases)
- [Commits](https://github.com/SonarSource/eslint-plugin-sonarjs/compare/0.24.0...0.25.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-sonarjs
  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>
2024-03-29 12:06:21 +01:00
d1e06b1c7e web: bump glob from 10.3.10 to 10.3.12 in /web (#9069)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.3.10 to 10.3.12.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.3.10...v10.3.12)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:06:09 +01:00
458b2b5c55 web: bump the rollup group in /web with 3 updates (#9067)
Bumps the rollup group in /web with 3 updates: [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup), [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup) and [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup).


Updates `@rollup/rollup-darwin-arm64` from 4.13.1 to 4.13.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.1...v4.13.2)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.13.1 to 4.13.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.1...v4.13.2)

Updates `@rollup/rollup-linux-x64-gnu` from 4.13.1 to 4.13.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.1...v4.13.2)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:06:02 +01:00
c0b1cd7674 web: bump the eslint group in /tests/wdio with 1 update (#9071)
Bumps the eslint group in /tests/wdio with 1 update: [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs).


Updates `eslint-plugin-sonarjs` from 0.24.0 to 0.25.0
- [Release notes](https://github.com/SonarSource/eslint-plugin-sonarjs/releases)
- [Commits](https://github.com/SonarSource/eslint-plugin-sonarjs/compare/0.24.0...0.25.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-sonarjs
  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>
2024-03-29 12:05:52 +01:00
8305a52ae2 core: bump webauthn from 2.0.0 to 2.1.0 (#9070)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.0.0...v2.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:05:40 +01:00
b77cdfe96b core: bump sentry-sdk from 1.43.0 to 1.44.0 (#9073)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.43.0...1.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:05:32 +01:00
0dcb261b4c core: bump requests-mock from 1.12.0 to 1.12.1 (#9072)
Bumps [requests-mock](https://github.com/jamielennox/requests-mock) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/jamielennox/requests-mock/releases)
- [Commits](https://github.com/jamielennox/requests-mock/compare/1.12.0...1.12.1)

---
updated-dependencies:
- dependency-name: requests-mock
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 12:04:46 +01:00
46bddbf067 web: bump API Client version (#9061)
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>
2024-03-28 17:37:16 +01:00
b8b6c0cd98 events: rework log messages returned from API and their rendering (#8770)
* events: initial log rework

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

* add migration code

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-03-28 17:34:34 +01:00
64fbbcf3e8 website/docs: update airgapped config (#9049)
* website/docs: update airgapped config

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

* fix immich urls

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-03-28 11:46:59 +01:00
a4c6b76686 website: bump @types/react from 18.2.72 to 18.2.73 in /website (#9052)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.72 to 18.2.73.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:46:13 +01:00
c8c7f77813 web: bump the rollup group in /web with 3 updates (#9053)
Bumps the rollup group in /web with 3 updates: [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup), [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup) and [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup).


Updates `@rollup/rollup-darwin-arm64` from 4.13.0 to 4.13.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.0...v4.13.1)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.13.0 to 4.13.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.0...v4.13.1)

Updates `@rollup/rollup-linux-x64-gnu` from 4.13.0 to 4.13.1
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.13.0...v4.13.1)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:46:06 +01:00
dde4314127 core: bump django-filter from 24.1 to 24.2 (#9055)
Bumps [django-filter](https://github.com/carltongibson/django-filter) from 24.1 to 24.2.
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/24.1...24.2)

---
updated-dependencies:
- dependency-name: django-filter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:45:56 +01:00
0b620f54f3 core: bump requests-mock from 1.11.0 to 1.12.0 (#9056)
Bumps [requests-mock](https://github.com/jamielennox/requests-mock) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/jamielennox/requests-mock/releases)
- [Commits](https://github.com/jamielennox/requests-mock/compare/1.11.0...1.12.0)

---
updated-dependencies:
- dependency-name: requests-mock
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:45:50 +01:00
dc10ab0e66 core: bump selenium from 4.18.1 to 4.19.0 (#9057)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.18.1 to 4.19.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.18.1...selenium-4.19.0)

---
updated-dependencies:
- dependency-name: selenium
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:45:43 +01:00
8d92e3d78d web: bump chromedriver from 123.0.0 to 123.0.1 in /tests/wdio (#9058)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 123.0.0 to 123.0.1.
- [Commits](https://github.com/giggio/node-chromedriver/compare/123.0.0...123.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 11:45:36 +01:00
ae66df6d9a website/integrations: wekan: fix properties (#9047) 2024-03-27 20:45:02 +01:00
ed3108fbd4 web: a few minor bugfixes and lintfixes (#9044)
* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* web: just a few minor bugfixes and lintfixes

While investigating the viability of using ESLint 9, I found a few bugs.

The one major bug was found in the error handling code, where a comparison was
automatically invalid and would never realize "true."

A sequence used in our Storybook support code to generate unique IDs for
applications and providers had an annoying ambiguity:

```
new Array(length).fill(" ")
```

Lint states (and I agree):

> It's not clear whether the argument is meant to be the length of the array or
> the only element. If the argument is the array's length, consider using
> `Array.from({ length: n })`. If the argument is the only element, use
> `[element]`."

It's the former, and I intended as much.

Aside from those, a few over-wrought uses of the spread operator were removed.

* Fat-finger error. Thank gnu I double-check my PRs before I move them out of draft!
2024-03-27 09:00:42 -07:00
f2199f1712 website/integrations: add documentation for OIDC setup with Xen Orchestra (#9000)
* website/integrations: add documentation for OIDC setup with Xen Orchestra

* Dot removed

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Dot added

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Update website/integrations/services/xen-orchestra/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Update website/integrations/services/xen-orchestra/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Update website/integrations/services/xen-orchestra/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Update website/integrations/services/xen-orchestra/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* Update website/integrations/services/xen-orchestra/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>

* moved XO-configuration-values into a list instead of having numerous steps

* remove config params, that are retrieved by Auto-discovery URl anyways

* add information about user mapping using the e-mail-address

* changed note since auto-user-creation is implemented in the XO OIDC plugin

* fix typos

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

---------

Signed-off-by: pgumpoldsberger <60177408+pgumpoldsberger@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-03-27 15:49:11 +01:00
e5810b31c5 website: bump @types/react from 18.2.70 to 18.2.72 in /website (#9041)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.70 to 18.2.72.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 12:57:21 +01:00
d8b6a06522 core: bump goauthentik.io/api/v3 from 3.2024022.5 to 3.2024022.6 (#9042)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024022.5 to 3.2024022.6.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024022.5...v3.2024022.6)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 12:57:08 +01:00
c8ab6c728d web: fix markdown rendering bug for alerts (#9037)
* web: fix esbuild issue with style sheets

Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.

* web:fix markdown rendering bug for alerts

The move to using showdown dynamically, at run-time, resulted in a parse error
where our alerts were not being decorated with the right syntax. This patch
recognizes the new `:::info` EOL syntax (and leaves the old one in-place, as
well) and the rendering is now correct.

Our complexity has reached the point where eslint now needs the memory increase.
2024-03-26 23:30:20 +01:00
e854623967 web: bump API Client version (#9035)
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>
2024-03-26 15:55:11 +01:00
69 changed files with 1095 additions and 554 deletions

View File

@ -19,8 +19,6 @@ from guardian.models import UserObjectPermission
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import BoundLogger, get_logger
from structlog.testing import capture_logs
from structlog.types import EventDict
from yaml import load
from authentik.blueprints.v1.common import (
@ -42,6 +40,7 @@ from authentik.core.models import (
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import LicenseUsage
from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.events.logs import LogEvent, capture_logs
from authentik.events.models import SystemTask
from authentik.events.utils import cleanse_dict
from authentik.flows.models import FlowToken, Stage
@ -161,7 +160,7 @@ class Importer:
def updater(value) -> Any:
if value in self.__pk_map:
self.logger.debug("updating reference in entry", value=value)
self.logger.debug("Updating reference in entry", value=value)
return self.__pk_map[value]
return value
@ -250,7 +249,7 @@ class Importer:
model_instance = existing_models.first()
if not isinstance(model(), BaseMetaModel) and model_instance:
self.logger.debug(
"initialise serializer with instance",
"Initialise serializer with instance",
model=model,
instance=model_instance,
pk=model_instance.pk,
@ -260,14 +259,14 @@ class Importer:
elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED:
raise EntryInvalidError.from_entry(
(
f"state is set to {BlueprintEntryDesiredState.MUST_CREATED} "
f"State is set to {BlueprintEntryDesiredState.MUST_CREATED} "
"and object exists already",
),
entry,
)
else:
self.logger.debug(
"initialised new serializer instance",
"Initialised new serializer instance",
model=model,
**cleanse_dict(updated_identifiers),
)
@ -324,7 +323,7 @@ class Importer:
model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
except LookupError:
self.logger.warning(
"app or model does not exist", app=model_app_label, model=model_name
"App or Model does not exist", app=model_app_label, model=model_name
)
return False
# Validate each single entry
@ -336,7 +335,7 @@ class Importer:
if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT:
serializer = exc.serializer
else:
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
self.logger.warning(f"Entry invalid: {exc}", entry=entry, error=exc)
if raise_errors:
raise exc
return False
@ -356,14 +355,14 @@ class Importer:
and state == BlueprintEntryDesiredState.CREATED
):
self.logger.debug(
"instance exists, skipping",
"Instance exists, skipping",
model=model,
instance=instance,
pk=instance.pk,
)
else:
instance = serializer.save()
self.logger.debug("updated model", model=instance)
self.logger.debug("Updated model", model=instance)
if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = instance.pk
entry._state = BlueprintEntryState(instance)
@ -371,12 +370,12 @@ class Importer:
instance: Model | None = serializer.instance
if instance.pk:
instance.delete()
self.logger.debug("deleted model", mode=instance)
self.logger.debug("Deleted model", mode=instance)
continue
self.logger.debug("entry to delete with no instance, skipping")
self.logger.debug("Entry to delete with no instance, skipping")
return True
def validate(self, raise_validation_errors=False) -> tuple[bool, list[EventDict]]:
def validate(self, raise_validation_errors=False) -> tuple[bool, list[LogEvent]]:
"""Validate loaded blueprint export, ensure all models are allowed
and serializers have no errors"""
self.logger.debug("Starting blueprint import validation")
@ -390,9 +389,7 @@ class Importer:
):
successful = self._apply_models(raise_errors=raise_validation_errors)
if not successful:
self.logger.debug("Blueprint validation failed")
for log in logs:
getattr(self.logger, log.get("log_level"))(**log)
self.logger.warning("Blueprint validation failed")
self.logger.debug("Finished blueprint import validation")
self._import = orig_import
return successful, logs

View File

@ -30,6 +30,7 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E
from authentik.blueprints.v1.importer import Importer
from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE
from authentik.blueprints.v1.oci import OCI_PREFIX
from authentik.events.logs import capture_logs
from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask, prefill_task
from authentik.events.utils import sanitize_dict
@ -211,14 +212,15 @@ def apply_blueprint(self: SystemTask, instance_pk: str):
if not valid:
instance.status = BlueprintInstanceStatus.ERROR
instance.save()
self.set_status(TaskStatus.ERROR, *[x["event"] for x in logs])
return
applied = importer.apply()
if not applied:
instance.status = BlueprintInstanceStatus.ERROR
instance.save()
self.set_status(TaskStatus.ERROR, "Failed to apply")
self.set_status(TaskStatus.ERROR, *logs)
return
with capture_logs() as logs:
applied = importer.apply()
if not applied:
instance.status = BlueprintInstanceStatus.ERROR
instance.save()
self.set_status(TaskStatus.ERROR, *logs)
return
instance.status = BlueprintInstanceStatus.SUCCESSFUL
instance.last_applied_hash = file_hash
instance.last_applied = now()

View File

@ -20,15 +20,14 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik.admin.api.metrics import CoordinateSerializer
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Application, User
from authentik.events.logs import LogEventSerializer, capture_logs
from authentik.events.models import EventAction
from authentik.events.utils import sanitize_dict
from authentik.lib.utils.file import (
FilePathSerializer,
FileUploadSerializer,
@ -182,9 +181,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if request.user.is_superuser:
log_messages = []
for log in logs:
if log.get("process", "") == "PolicyProcess":
if log.attributes.get("process", "") == "PolicyProcess":
continue
log_messages.append(sanitize_dict(log))
log_messages.append(LogEventSerializer(log).data)
result.log_messages = log_messages
response = PolicyTestResultSerializer(result)
return Response(response.data)

View File

@ -12,7 +12,6 @@ from rest_framework.fields import (
ChoiceField,
DateTimeField,
FloatField,
ListField,
SerializerMethodField,
)
from rest_framework.request import Request
@ -21,6 +20,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
from structlog.stdlib import get_logger
from authentik.events.logs import LogEventSerializer
from authentik.events.models import SystemTask, TaskStatus
from authentik.rbac.decorators import permission_required
@ -39,7 +39,7 @@ class SystemTaskSerializer(ModelSerializer):
duration = FloatField(read_only=True)
status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus])
messages = ListField(child=CharField())
messages = LogEventSerializer(many=True)
def get_full_name(self, instance: SystemTask) -> str:
"""Get full name with UID"""

82
authentik/events/logs.py Normal file
View File

@ -0,0 +1,82 @@
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from django.utils.timezone import now
from rest_framework.fields import CharField, ChoiceField, DateTimeField, DictField
from structlog import configure, get_config
from structlog.stdlib import NAME_TO_LEVEL, ProcessorFormatter
from structlog.testing import LogCapture
from structlog.types import EventDict
from authentik.core.api.utils import PassiveSerializer
from authentik.events.utils import sanitize_dict
@dataclass()
class LogEvent:
event: str
log_level: str
logger: str
timestamp: datetime = field(default_factory=now)
attributes: dict[str, Any] = field(default_factory=dict)
@staticmethod
def from_event_dict(item: EventDict) -> "LogEvent":
event = item.pop("event")
log_level = item.pop("level").lower()
timestamp = datetime.fromisoformat(item.pop("timestamp"))
item.pop("pid", None)
# Sometimes log entries have both `level` and `log_level` set, but `level` is always set
item.pop("log_level", None)
return LogEvent(
event, log_level, item.pop("logger"), timestamp, attributes=sanitize_dict(item)
)
class LogEventSerializer(PassiveSerializer):
"""Single log message with all context logged."""
timestamp = DateTimeField()
log_level = ChoiceField(choices=tuple((x, x) for x in NAME_TO_LEVEL.keys()))
logger = CharField()
event = CharField()
attributes = DictField()
# TODO(2024.6?): This is a migration helper to return a correct API response for logs that
# have been saved in an older format (mostly just list[str] with just the messages)
def to_representation(self, instance):
if isinstance(instance, str):
instance = LogEvent(instance, "", "")
elif isinstance(instance, list):
instance = [LogEvent(x, "", "") for x in instance]
return super().to_representation(instance)
@contextmanager
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
"""Capture log entries created"""
logs = []
cap = LogCapture()
# Modify `_Configuration.default_processors` set via `configure` but always
# keep the list instance intact to not break references held by bound
# loggers.
processors: list = get_config()["processors"]
old_processors = processors.copy()
try:
# clear processors list and use LogCapture for testing
if ProcessorFormatter.wrap_for_formatter in processors:
processors.remove(ProcessorFormatter.wrap_for_formatter)
processors.append(cap)
configure(processors=processors)
yield logs
for raw_log in cap.entries:
logs.append(LogEvent.from_event_dict(raw_log))
finally:
# remove LogCapture and restore original processors
processors.clear()
processors.extend(old_processors)
configure(processors=processors)

View File

@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger
from tenant_schemas_celery.task import TenantTask
from authentik.events.logs import LogEvent
from authentik.events.models import Event, EventAction, TaskStatus
from authentik.events.models import SystemTask as DBSystemTask
from authentik.events.utils import sanitize_item
@ -24,7 +25,7 @@ class SystemTask(TenantTask):
save_on_success: bool
_status: TaskStatus
_messages: list[str]
_messages: list[LogEvent]
_uid: str | None
# Precise start time from perf_counter
@ -44,15 +45,20 @@ class SystemTask(TenantTask):
"""Set UID, so in the case of an unexpected error its saved correctly"""
self._uid = uid
def set_status(self, status: TaskStatus, *messages: str):
def set_status(self, status: TaskStatus, *messages: LogEvent):
"""Set result for current run, will overwrite previous result."""
self._status = status
self._messages = messages
self._messages = list(messages)
for idx, msg in enumerate(self._messages):
if not isinstance(msg, LogEvent):
self._messages[idx] = LogEvent(msg, logger=self.__name__, log_level="info")
def set_error(self, exception: Exception):
"""Set result to error and save exception"""
self._status = TaskStatus.ERROR
self._messages = [exception_to_string(exception)]
self._messages = [
LogEvent(exception_to_string(exception), logger=self.__name__, log_level="error")
]
def before_start(self, task_id, args, kwargs):
self._start_precise = perf_counter()
@ -98,8 +104,7 @@ class SystemTask(TenantTask):
def on_failure(self, exc, task_id, args, kwargs, einfo):
super().on_failure(exc, task_id, args, kwargs, einfo=einfo)
if not self._status:
self._status = TaskStatus.ERROR
self._messages = exception_to_string(exc)
self.set_error(exc)
DBSystemTask.objects.update_or_create(
name=self.__name__,
uid=self._uid,

View File

@ -47,3 +47,4 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet):
filterset_fields = "__all__"
search_fields = ["stage__name"]
ordering = ["order"]
ordering_fields = ["order", "stage__name"]

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DictField, ListField, ReadOnlyField
from rest_framework.fields import BooleanField, CharField, ReadOnlyField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@ -19,7 +19,7 @@ from authentik.blueprints.v1.exporter import FlowExporter
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, Importer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer
from authentik.events.utils import sanitize_dict
from authentik.events.logs import LogEventSerializer
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
@ -107,7 +107,7 @@ class FlowSetSerializer(FlowSerializer):
class FlowImportResultSerializer(PassiveSerializer):
"""Logs of an attempted flow import"""
logs = ListField(child=DictField(), read_only=True)
logs = LogEventSerializer(many=True, read_only=True)
success = BooleanField(read_only=True)
@ -184,7 +184,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
importer = Importer.from_string(file.read().decode())
valid, logs = importer.validate()
import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs]
import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs]
import_response.initial_data["success"] = valid
import_response.is_valid()
if not valid:

View File

@ -59,11 +59,11 @@ class FlowPlan:
markers: list[StageMarker] = field(default_factory=list)
def append_stage(self, stage: Stage, marker: StageMarker | None = None):
"""Append `stage` to all stages, optionally with stage marker"""
"""Append `stage` to the end of the plan, optionally with stage marker"""
return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: StageMarker | None = None):
"""Append `stage` to all stages, optionally with stage marker"""
"""Append `stage` to the end of the plan, optionally with stage marker"""
self.bindings.append(binding)
self.markers.append(marker or StageMarker())

View File

@ -450,7 +450,7 @@ class FlowExecutorView(APIView):
return to_stage_response(self.request, challenge_view.get(self.request))
def cancel(self):
"""Cancel current execution and return a redirect"""
"""Cancel current flow execution"""
keys_to_delete = [
SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN,

View File

@ -3,9 +3,9 @@
from dataclasses import dataclass
from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik import __version__, get_build_hash
from authentik.events.logs import LogEvent, capture_logs
from authentik.lib.config import CONFIG
from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import (
@ -63,21 +63,21 @@ class BaseController:
"""Called by scheduled task to reconcile deployment/service/etc"""
raise NotImplementedError
def up_with_logs(self) -> list[str]:
def up_with_logs(self) -> list[LogEvent]:
"""Call .up() but capture all log output and return it."""
with capture_logs() as logs:
self.up()
return [x["event"] for x in logs]
return logs
def down(self):
"""Handler to delete everything we've created"""
raise NotImplementedError
def down_with_logs(self) -> list[str]:
def down_with_logs(self) -> list[LogEvent]:
"""Call .down() but capture all log output and return it."""
with capture_logs() as logs:
self.down()
return [x["event"] for x in logs]
return logs
def __enter__(self):
return self

View File

@ -9,10 +9,10 @@ from kubernetes.client.exceptions import OpenApiException
from kubernetes.config.config_exception import ConfigException
from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict
from structlog.testing import capture_logs
from urllib3.exceptions import HTTPError
from yaml import dump_all
from authentik.events.logs import LogEvent, capture_logs
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
@ -91,7 +91,7 @@ class KubernetesController(BaseController):
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def up_with_logs(self) -> list[str]:
def up_with_logs(self) -> list[LogEvent]:
try:
all_logs = []
for reconcile_key in self.reconcile_order:
@ -104,7 +104,9 @@ class KubernetesController(BaseController):
continue
reconciler = reconciler_cls(self)
reconciler.up()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
for log in logs:
log.logger = reconcile_key.title()
all_logs.extend(logs)
return all_logs
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
@ -122,7 +124,7 @@ class KubernetesController(BaseController):
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def down_with_logs(self) -> list[str]:
def down_with_logs(self) -> list[LogEvent]:
try:
all_logs = []
for reconcile_key in self.reconcile_order:
@ -135,7 +137,9 @@ class KubernetesController(BaseController):
continue
reconciler = reconciler_cls(self)
reconciler.down()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
for log in logs:
log.logger = reconcile_key.title()
all_logs.extend(logs)
return all_logs
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc

View File

@ -149,10 +149,8 @@ def outpost_controller(
if not controller_type:
return
with controller_type(outpost, outpost.service_connection) as controller:
logs = getattr(controller, f"{action}_with_logs")()
LOGGER.debug("---------------Outpost Controller logs starting----------------")
for log in logs:
LOGGER.debug(log)
logs = getattr(controller, f"{action}_with_logs")()
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_error(exc)

View File

@ -1,10 +1,11 @@
"""Serializer for policy execution"""
from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.fields import BooleanField, CharField, ListField
from rest_framework.relations import PrimaryKeyRelatedField
from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User
from authentik.events.logs import LogEventSerializer
class PolicyTestSerializer(PassiveSerializer):
@ -19,4 +20,4 @@ class PolicyTestResultSerializer(PassiveSerializer):
passing = BooleanField()
messages = ListField(child=CharField(), read_only=True)
log_messages = ListField(child=DictField(), read_only=True)
log_messages = LogEventSerializer(many=True, read_only=True)

View File

@ -11,12 +11,11 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik.core.api.applications import user_app_cache_key
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer
from authentik.events.utils import sanitize_dict
from authentik.events.logs import LogEventSerializer, capture_logs
from authentik.lib.utils.reflection import all_subclasses
from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer
from authentik.policies.models import Policy, PolicyBinding
@ -166,9 +165,9 @@ class PolicyViewSet(
result = proc.execute()
log_messages = []
for log in logs:
if log.get("process", "") == "PolicyProcess":
if log.attributes.get("process", "") == "PolicyProcess":
continue
log_messages.append(sanitize_dict(log))
log_messages.append(LogEventSerializer(log).data)
result.log_messages = log_messages
response = PolicyTestResultSerializer(result)
return Response(response.data)

View File

@ -13,6 +13,7 @@ from authentik.events.context_processors.base import get_context_processors
if TYPE_CHECKING:
from authentik.core.models import User
from authentik.events.logs import LogEvent
from authentik.policies.models import PolicyBinding
LOGGER = get_logger()
@ -74,7 +75,7 @@ class PolicyResult:
source_binding: PolicyBinding | None
source_results: list[PolicyResult] | None
log_messages: list[dict] | None
log_messages: list[LogEvent] | None
def __init__(self, passing: bool, *messages: str):
self.passing = passing

View File

@ -25,7 +25,7 @@ class OAuthDeviceCodeFinishChallengeResponse(ChallengeResponse):
class OAuthDeviceCodeFinishStage(ChallengeStageView):
"""Stage show at the end of a device flow"""
"""Stage to finish the OAuth device code flow"""
response_class = OAuthDeviceCodeFinishChallengeResponse

View File

@ -3,7 +3,7 @@
from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _
from django.views import View
from rest_framework.exceptions import ErrorDetail
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, IntegerField
from structlog.stdlib import get_logger
@ -57,6 +57,7 @@ def validate_code(code: int, request: HttpRequest) -> HttpResponse | None:
scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider)
planner = FlowPlanner(token.provider.authorization_flow)
planner.allow_empty_flows = True
planner.use_cache = False
try:
plan = planner.plan(
request,
@ -128,6 +129,13 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
code = IntegerField()
component = CharField(default="ak-provider-oauth2-device-code")
def validate_code(self, code: int) -> HttpResponse | None:
"""Validate code and save the returned http response"""
response = validate_code(code, self.stage.request)
if not response:
raise ValidationError("Invalid code", "invalid")
return response
class OAuthDeviceCodeStage(ChallengeStageView):
"""Flow challenge for users to enter device codes"""
@ -143,12 +151,4 @@ class OAuthDeviceCodeStage(ChallengeStageView):
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
code = response.validated_data["code"]
validation = validate_code(code, self.request)
if not validation:
response._errors.setdefault("code", [])
response._errors["code"].append(ErrorDetail(_("Invalid code"), "invalid"))
return self.challenge_invalid(response)
# Run cancel to cleanup the current flow
self.executor.cancel()
return validation
return response.validated_data["code"]

2
go.mod
View File

@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024022.5
goauthentik.io/api/v3 v3.2024022.7
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.0

4
go.sum
View File

@ -280,8 +280,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.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024022.5 h1:z1ZaVY/UpwpHAghf/PyYRSOQT7U9g8E2N23YlRB5BJQ=
goauthentik.io/api/v3 v3.2024022.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2024022.7 h1:VR9OmcZvTzPSjit2Dx2EoHrLc9v9XRyjPXNpnGISWWM=
goauthentik.io/api/v3 v3.2024022.7/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

@ -35,7 +35,7 @@ type ProviderInstance struct {
cert *tls.Certificate
certUUID string
outpostName string
outpostPk int32
providerPk int32
searchAllowedGroups []*strfmt.UUID
boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags

View File

@ -22,7 +22,7 @@ import (
func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
for _, p := range ls.providers {
if p.outpostPk == pk {
if p.providerPk == pk {
return p
}
}
@ -83,7 +83,7 @@ func (ls *LDAPServer) Refresh() error {
gidStartNumber: provider.GetGidStartNumber(),
mfaSupport: provider.GetMfaSupport(),
outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk,
providerPk: provider.Pk,
}
if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp)

View File

@ -6,8 +6,10 @@ import (
"net"
"sort"
"strings"
"sync"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/flags"
)
func parseCIDRs(raw string) []*net.IPNet {
@ -29,6 +31,25 @@ func parseCIDRs(raw string) []*net.IPNet {
return cidrs
}
func (rs *RadiusServer) getCurrentProvider(pk int32) *ProviderInstance {
for _, p := range rs.providers {
if p.providerPk == pk {
return p
}
}
return nil
}
func (rs *RadiusServer) getInvalidationFlow() string {
req, _, err := rs.ac.Client.CoreApi.CoreBrandsCurrentRetrieve(context.Background()).Execute()
if err != nil {
rs.log.WithError(err).Warning("failed to fetch brand config")
return ""
}
flow := req.GetFlowInvalidation()
return flow
}
func (rs *RadiusServer) Refresh() error {
outposts, _, err := rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()).Execute()
if err != nil {
@ -37,17 +58,33 @@ func (rs *RadiusServer) Refresh() error {
if len(outposts.Results) < 1 {
return errors.New("no radius provider defined")
}
invalidationFlow := rs.getInvalidationFlow()
providers := make([]*ProviderInstance, len(outposts.Results))
for idx, provider := range outposts.Results {
logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name)
// Get existing instance so we can transfer boundUsers
existing := rs.getCurrentProvider(provider.Pk)
usersMutex := &sync.RWMutex{}
users := make(map[string]*flags.UserFlags)
if existing != nil {
usersMutex = existing.boundUsersMutex
// Shallow copy, no need to lock
users = existing.boundUsers
}
providers[idx] = &ProviderInstance{
SharedSecret: []byte(provider.GetSharedSecret()),
ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
MFASupport: provider.GetMfaSupport(),
appSlug: provider.ApplicationSlug,
flowSlug: provider.AuthFlowSlug,
s: rs,
log: logger,
SharedSecret: []byte(provider.GetSharedSecret()),
ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
MFASupport: provider.GetMfaSupport(),
appSlug: provider.ApplicationSlug,
authenticationFlowSlug: provider.AuthFlowSlug,
invalidationFlowSlug: invalidationFlow,
s: rs,
log: logger,
providerPk: provider.Pk,
boundUsersMutex: usersMutex,
boundUsers: users,
}
}
rs.providers = providers

View File

@ -4,15 +4,17 @@ import (
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/radius/metrics"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2866"
)
func (rs *RadiusServer) Handle_AccessRequest(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{
fe := flow.NewFlowExecutor(r.Context(), r.pi.authenticationFlowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
"username": username,
"client": r.RemoteAddr(),
"requestId": r.ID(),
@ -64,5 +66,28 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
}).Inc()
return
}
_ = w.Write(r.Response(radius.CodeAccessAccept))
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(r.Context()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"type": "bind",
"reason": "user_info_fail",
}).Inc()
r.Log().WithError(err).Warning("failed to get user info")
return
}
response := r.Response(radius.CodeAccessAccept)
_ = rfc2866.AcctSessionID_SetString(response, fe.GetSession().String())
r.pi.boundUsersMutex.Lock()
r.pi.boundUsers[fe.GetSession().String()] = &flags.UserFlags{
Session: fe.GetSession(),
UserPk: userInfo.Original.Pk,
}
r.pi.boundUsersMutex.Unlock()
err = w.Write(response)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
}

View File

@ -0,0 +1,54 @@
package radius
import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/flags"
"layeh.com/radius"
"layeh.com/radius/rfc2866"
)
func (rs *RadiusServer) Handle_DisconnectRequest(w radius.ResponseWriter, r *RadiusRequest) {
session := rfc2866.AcctSessionID_GetString(r.Packet)
sendFailResponse := func() {
failResponse := r.Response(radius.CodeDisconnectACK)
err := w.Write(failResponse)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
}
r.pi.boundUsersMutex.Lock()
var f *flags.UserFlags
if ff, ok := r.pi.boundUsers[session]; !ok {
r.pi.boundUsersMutex.Unlock()
sendFailResponse()
return
} else {
f = ff
}
r.pi.boundUsersMutex.Unlock()
fe := flow.NewFlowExecutor(r.Context(), r.pi.invalidationFlowSlug, rs.ac.Client.GetConfig(), log.Fields{
"client": r.RemoteAddr(),
"requestId": r.ID(),
})
fe.SetSession(f.Session)
fe.DelegateClientIP(r.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/radius", "true")
_, err := fe.Execute()
if err != nil {
r.log.WithError(err).Warning("failed to logout user")
sendFailResponse()
return
}
r.pi.boundUsersMutex.Lock()
delete(r.pi.boundUsers, session)
r.pi.boundUsersMutex.Unlock()
response := r.Response(radius.CodeDisconnectACK)
err = w.Write(response)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
}

View File

@ -74,7 +74,12 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
}
nr.pi = pi
if nr.Code == radius.CodeAccessRequest {
switch nr.Code {
case radius.CodeAccessRequest:
rs.Handle_AccessRequest(w, nr)
case radius.CodeDisconnectRequest:
rs.Handle_DisconnectRequest(w, nr)
default:
nr.Log().WithField("code", nr.Code.String()).Debug("Unsupported packet code")
}
}

View File

@ -9,20 +9,25 @@ import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/radius/metrics"
"layeh.com/radius"
)
type ProviderInstance struct {
ClientNetworks []*net.IPNet
SharedSecret []byte
MFASupport bool
ClientNetworks []*net.IPNet
SharedSecret []byte
MFASupport bool
boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags
providerPk int32
appSlug string
flowSlug string
s *RadiusServer
log *log.Entry
appSlug string
authenticationFlowSlug string
invalidationFlowSlug string
s *RadiusServer
log *log.Entry
}
type RadiusServer struct {

36
poetry.lock generated
View File

@ -1142,13 +1142,13 @@ bcrypt = ["bcrypt"]
[[package]]
name = "django-filter"
version = "24.1"
version = "24.2"
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-filter-24.1.tar.gz", hash = "sha256:65cb43ce272077e5ac6aae1054d76c121cd6b552e296a82a13921e9371baf8c1"},
{file = "django_filter-24.1-py3-none-any.whl", hash = "sha256:335bcae6cbd3e984b024841070f567b22faea57594f27d37c52f8f131f8d8621"},
{file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"},
{file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"},
]
[package.dependencies]
@ -3332,22 +3332,20 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-mock"
version = "1.11.0"
version = "1.12.1"
description = "Mock out responses from the requests package"
optional = false
python-versions = "*"
python-versions = ">=3.5"
files = [
{file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"},
{file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"},
{file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"},
{file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"},
]
[package.dependencies]
requests = ">=2.3,<3"
six = "*"
requests = ">=2.22,<3"
[package.extras]
fixture = ["fixtures"]
test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"]
[[package]]
name = "requests-oauthlib"
@ -3552,13 +3550,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
[[package]]
name = "selenium"
version = "4.18.1"
version = "4.19.0"
description = ""
optional = false
python-versions = ">=3.8"
files = [
{file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"},
{file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"},
{file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"},
{file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"},
]
[package.dependencies]
@ -3570,13 +3568,13 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
[[package]]
name = "sentry-sdk"
version = "1.43.0"
version = "1.44.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = "*"
files = [
{file = "sentry-sdk-1.43.0.tar.gz", hash = "sha256:41df73af89d22921d8733714fb0fc5586c3461907e06688e6537d01a27e0e0f6"},
{file = "sentry_sdk-1.43.0-py2.py3-none-any.whl", hash = "sha256:8d768724839ca18d7b4c7463ef7528c40b7aa2bfbf7fe554d5f9a7c044acfd36"},
{file = "sentry-sdk-1.44.0.tar.gz", hash = "sha256:f7125a9235795811962d52ff796dc032cd1d0dd98b59beaced8380371cd9c13c"},
{file = "sentry_sdk-1.44.0-py2.py3-none-any.whl", hash = "sha256:eb65289da013ca92fad2694851ad2f086aa3825e808dc285bd7dcaf63602bb18"},
]
[package.dependencies]
@ -4203,13 +4201,13 @@ files = [
[[package]]
name = "webauthn"
version = "2.0.0"
version = "2.1.0"
description = "Pythonic WebAuthn"
optional = false
python-versions = "*"
files = [
{file = "webauthn-2.0.0-py3-none-any.whl", hash = "sha256:644dc68af5caaade06be6a2a2278775e85116e92dd755ad7a49d992d51c82033"},
{file = "webauthn-2.0.0.tar.gz", hash = "sha256:12cc1759da98668b8242badc37c4129df300f89d89f5c183fac80e7b33c41dfd"},
{file = "webauthn-2.1.0-py3-none-any.whl", hash = "sha256:9e1cf916e5ed7c01d54a6dfcc19dacbd2b87b81d2648f001b1fcbcb7aa2ff130"},
{file = "webauthn-2.1.0.tar.gz", hash = "sha256:b196a4246c2818820857ba195c6e6e5398c761117f2269e3d2deab11c7995fc4"},
]
[package.dependencies]

View File

@ -33782,8 +33782,7 @@ components:
logs:
type: array
items:
type: object
additionalProperties: {}
$ref: '#/components/schemas/LogEvent'
readOnly: true
success:
type: boolean
@ -35515,6 +35514,48 @@ components:
type: string
required:
- link
LogEvent:
type: object
description: Single log message with all context logged.
properties:
timestamp:
type: string
format: date-time
log_level:
$ref: '#/components/schemas/LogLevelEnum'
logger:
type: string
event:
type: string
attributes:
type: object
additionalProperties: {}
required:
- attributes
- event
- log_level
- logger
- timestamp
LogLevelEnum:
enum:
- critical
- exception
- error
- warn
- warning
- info
- debug
- notset
type: string
description: |-
* `critical` - critical
* `exception` - exception
* `error` - error
* `warn` - warn
* `warning` - warning
* `info` - info
* `debug` - debug
* `notset` - notset
LoginChallengeTypes:
oneOf:
- $ref: '#/components/schemas/RedirectChallenge'
@ -41309,8 +41350,7 @@ components:
log_messages:
type: array
items:
type: object
additionalProperties: {}
$ref: '#/components/schemas/LogEvent'
readOnly: true
required:
- log_messages
@ -44324,7 +44364,7 @@ components:
messages:
type: array
items:
type: string
$ref: '#/components/schemas/LogEvent'
required:
- description
- duration

View File

@ -26,12 +26,6 @@ class TestProxyKubernetes(TestCase):
outpost_connection_discovery()
self.controller = None
def tearDown(self) -> None:
if self.controller:
for log in self.controller.down_with_logs():
LOGGER.info(log)
return super().tearDown()
@pytest.mark.timeout(120)
def test_kubernetes_controller_static(self):
"""Test Kubernetes Controller"""

View File

@ -6,7 +6,7 @@
"": {
"name": "@goauthentik/web-tests",
"dependencies": {
"chromedriver": "^123.0.0"
"chromedriver": "^123.0.1"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
@ -18,7 +18,7 @@
"@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-sonarjs": "^0.25.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
@ -2084,9 +2084,9 @@
}
},
"node_modules/chromedriver": {
"version": "123.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.0.tgz",
"integrity": "sha512-OE9mpxXwbFy5LncAisqXm1aEzuLPtEMORIxyYIn9uT7N8rquJWyoip6w9Rytub3o2gnynW9+PFOTPVTldaYrtw==",
"version": "123.0.1",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz",
"integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==",
"hasInstallScript": true,
"dependencies": {
"@testim/chrome-version": "^1.1.4",
@ -3114,9 +3114,9 @@
}
},
"node_modules/eslint-plugin-sonarjs": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz",
"integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz",
"integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==",
"dev": true,
"engines": {
"node": ">=16"

View File

@ -12,7 +12,7 @@
"@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-sonarjs": "^0.25.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
@ -32,6 +32,6 @@
"node": ">=20"
},
"dependencies": {
"chromedriver": "^123.0.0"
"chromedriver": "^123.0.1"
}
}

17
web/docs/Changelog.md Normal file
View File

@ -0,0 +1,17 @@
### 2024-03-26T09:25:06-0700
Split the tsconfig file into a base and build variant.
Lesson: This lesson is stored here and not in a comment in tsconfig.json because
JSON doesn't like comments. Doug Crockford's purity requirement has doomed an
entire generation to keeping its human-facing meta somewhere other than in the
file where it belongs.
Lesson: The `extend` command of tsconfig has an unexpected behavior. It is
neither a merge or a replace, but some mixture of the two. The buildfile's
`compilerOptions` is not a full replacement; instead, each of _its_ top-level
fields is a replacement for what is found in the basefile. So while you don't
need to include _everything_ in a `compilerOptions` field if you want to change
one thing, if you want to modify _one_ path in `compilerOptions.path`, you must
include the entire `compilerOptions.path` collection in your buildfile.
g

193
web/package-lock.json generated
View File

@ -11,13 +11,13 @@
"dependencies": {
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.4",
"@codemirror/lang-python": "^6.1.5",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.3.3",
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.5",
"@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2024.2.2-1711369360",
"@goauthentik/api": "^2024.2.2-1711643691",
"@lit-labs/task": "^3.1.0",
"@lit/context": "^1.1.0",
"@lit/localize": "^0.12.1",
@ -25,7 +25,7 @@
"@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.108.0",
"@sentry/browser": "^7.109.0",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"chart.js": "^4.4.2",
@ -59,7 +59,7 @@
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.15",
"@spotlightjs/spotlight": "^1.2.16",
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
"@storybook/api": "^7.6.17",
@ -84,10 +84,10 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-sonarjs": "^0.25.0",
"eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0",
"glob": "^10.3.10",
"glob": "^10.3.12",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
@ -111,9 +111,9 @@
"@esbuild/darwin-arm64": "^0.20.1",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.20.1",
"@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0"
"@rollup/rollup-darwin-arm64": "4.13.2",
"@rollup/rollup-linux-arm64-gnu": "4.13.2",
"@rollup/rollup-linux-x64-gnu": "4.13.2"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -2164,8 +2164,9 @@
}
},
"node_modules/@codemirror/lang-python": {
"version": "6.1.4",
"license": "MIT",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.5.tgz",
"integrity": "sha512-hCm+8X6wrnXJCGf+QhmFu1AXkdTVG7dHy0Ly6SI1N3SRPptaMvwX6oNQonOXOMPvmcjiB0xq342KAxX3BYpijw==",
"dependencies": {
"@codemirror/autocomplete": "^6.3.2",
"@codemirror/language": "^6.8.0",
@ -2820,9 +2821,9 @@
}
},
"node_modules/@goauthentik/api": {
"version": "2024.2.2-1711369360",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.2.2-1711369360.tgz",
"integrity": "sha512-8/J6cfxzpaUyz+piZUXrxPZuAlJ9SxwNrH+Z8xSRLAVavmEjmRM+Oy2XJEIZLDbcBKhNEuE99xdOxq6il/FJVw=="
"version": "2024.2.2-1711643691",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.2.2-1711643691.tgz",
"integrity": "sha512-QHe+3gaNRkId54AuqndYNL0e5kG8nPlH4OjOYOPqOr3u70rxby63PBSPgSRKgqsigBpZufhQGsUBAPmpR8Hv0w=="
},
"node_modules/@hcaptcha/types": {
"version": "1.0.3",
@ -4259,9 +4260,9 @@
"peer": true
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
"integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
"cpu": [
"arm64"
],
@ -4299,9 +4300,9 @@
"peer": true
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
"integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
"cpu": [
"arm64"
],
@ -4339,9 +4340,9 @@
"peer": true
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
"integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
"cpu": [
"x64"
],
@ -4407,102 +4408,102 @@
"peer": true
},
"node_modules/@sentry-internal/feedback": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.108.0.tgz",
"integrity": "sha512-8JcgZEnk1uWrXJhsd3iRvFtEiVeaWOEhN0NZwhwQXHfvODqep6JtrkY1yCIyxbpA37aZmrPc2JhyotRERGfUjg==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.109.0.tgz",
"integrity": "sha512-EL7N++poxvJP9rYvh6vSu24tsKkOveNCcCj4IM7+irWPjsuD2GLYYlhp/A/Mtt9l7iqO4plvtiQU5HGk7smcTQ==",
"dependencies": {
"@sentry/core": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry/core": "7.109.0",
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.108.0.tgz",
"integrity": "sha512-R5tvjGqWUV5vSk0N1eBgVW7wIADinrkfDEBZ9FyKP2mXHBobsyNGt30heJDEqYmVqluRqjU2NuIRapsnnrpGnA==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.109.0.tgz",
"integrity": "sha512-Lh/K60kmloR6lkPUcQP0iamw7B/MdEUEx/ImAx4tUSMrLj+IoUEcq/ECgnnVyQkJq59+8nPEKrVLt7x6PUPEjw==",
"dependencies": {
"@sentry/core": "7.108.0",
"@sentry/replay": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry/core": "7.109.0",
"@sentry/replay": "7.109.0",
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.108.0.tgz",
"integrity": "sha512-zuK5XsTsb+U+hgn3SPetYDAogrXsM16U/LLoMW7+TlC6UjlHGYQvmX3o+M2vntejoU1QZS8m1bCAZSMWEypAEw==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.109.0.tgz",
"integrity": "sha512-PzK/joC5tCuh2R/PRh+7dp+uuZl7pTsBIjPhVZHMTtb9+ls65WkdZJ1/uKXPouyz8NOo9Xok7aEvEo9seongyw==",
"dependencies": {
"@sentry/core": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry/core": "7.109.0",
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.108.0.tgz",
"integrity": "sha512-FNpzsdTvGvdHJMUelqEouUXMZU7jC+dpN7CdT6IoHVVFEkoAgrjMVUhXZoQ/dmCkdKWHmFSQhJ8Fm6V+e9Aq0A==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.109.0.tgz",
"integrity": "sha512-yx+OFG+Ab9qUDDgV9ZDv8M9O9Mqr0fjKta/LMlWALYLjzkMvxsPlRPFj7oMBlHqOTVLDeg7lFYmsA8wyWQ8Z8g==",
"dependencies": {
"@sentry-internal/feedback": "7.108.0",
"@sentry-internal/replay-canvas": "7.108.0",
"@sentry-internal/tracing": "7.108.0",
"@sentry/core": "7.108.0",
"@sentry/replay": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry-internal/feedback": "7.109.0",
"@sentry-internal/replay-canvas": "7.109.0",
"@sentry-internal/tracing": "7.109.0",
"@sentry/core": "7.109.0",
"@sentry/replay": "7.109.0",
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/core": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.108.0.tgz",
"integrity": "sha512-I/VNZCFgLASxHZaD0EtxZRM34WG9w2gozqgrKGNMzAymwmQ3K9g/1qmBy4e6iS3YRptb7J5UhQkZQHrcwBbjWQ==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.109.0.tgz",
"integrity": "sha512-xwD4U0IlvvlE/x/g/W1I8b4Cfb16SsCMmiEuBf6XxvAa3OfWBxKoqLifb3GyrbxMC4LbIIZCN/SvLlnGJPgszA==",
"dependencies": {
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/replay": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.108.0.tgz",
"integrity": "sha512-jo8fDOzcZJclP1+4n9jUtVxTlBFT9hXwxhAMrhrt70FV/nfmCtYQMD3bzIj79nwbhUtFP6pN39JH1o7Xqt1hxQ==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.109.0.tgz",
"integrity": "sha512-hCDjbTNO7ErW/XsaBXlyHFsUhneyBUdTec1Swf98TFEfVqNsTs6q338aUcaR8dGRLbLrJ9YU9D1qKq++v5h2CA==",
"dependencies": {
"@sentry-internal/tracing": "7.108.0",
"@sentry/core": "7.108.0",
"@sentry/types": "7.108.0",
"@sentry/utils": "7.108.0"
"@sentry-internal/tracing": "7.109.0",
"@sentry/core": "7.109.0",
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/types": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.108.0.tgz",
"integrity": "sha512-bKtHITmBN3kqtqE5eVvL8mY8znM05vEodENwRpcm6TSrrBjC2RnwNWVwGstYDdHpNfFuKwC8mLY9bgMJcENo8g==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.109.0.tgz",
"integrity": "sha512-egCBnDv3YpVFoNzRLdP0soVrxVLCQ+rovREKJ1sw3rA2/MFH9WJ+DZZexsX89yeAFzy1IFsCp7/dEqudusml6g==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.108.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.108.0.tgz",
"integrity": "sha512-a45yEFD5qtgZaIFRAcFkG8C8lnDzn6t4LfLXuV4OafGAy/3ZAN3XN8wDnrruHkiUezSSANGsLg3bXaLW/JLvJw==",
"version": "7.109.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.109.0.tgz",
"integrity": "sha512-3RjxMOLMBwZ5VSiH84+o/3NY2An4Zldjz0EbfEQNRY9yffRiCPJSQiCJID8EoylCFOh/PAhPimBhqbtWJxX6iw==",
"dependencies": {
"@sentry/types": "7.108.0"
"@sentry/types": "7.109.0"
},
"engines": {
"node": ">=8"
@ -4514,9 +4515,9 @@
"license": "MIT"
},
"node_modules/@spotlightjs/overlay": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-1.8.1.tgz",
"integrity": "sha512-t8S2b6AxgDfDoPls3CU7uABLdKx3g8cCXQWEHOICC1i7MYUSQLFMDpWzFWTEjN0XA8MGwNf/QKNlZ/HhaKTzJw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-1.8.2.tgz",
"integrity": "sha512-g3pzaJFKK67pBIl72qSNoFJIfP/dmdFoSPWZZQW6MKAdU7IOY5yf3BB52xEc6iSfeLGG/KpYNThefpobX3hb7Q==",
"dev": true
},
"node_modules/@spotlightjs/sidecar": {
@ -4528,12 +4529,12 @@
}
},
"node_modules/@spotlightjs/spotlight": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-1.2.15.tgz",
"integrity": "sha512-M0VTAyameAsK9kjI9k31CehTLJMqUdOvv7DSOr27dcioytBV0uC0l8w7ngHWxdqCOTpbruEs8EIrbQ0T9b4YZQ==",
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-1.2.16.tgz",
"integrity": "sha512-grqK7Qwzz0zJKaM4+u/0DS81gEGKkUsKwXGY1kA07rXsbp6ilT62JWI1tQDgYHb1i3MbR2ch0EuMT476CAtA+A==",
"dev": true,
"dependencies": {
"@spotlightjs/overlay": "1.8.1",
"@spotlightjs/overlay": "1.8.2",
"@spotlightjs/sidecar": "1.4.0"
},
"bin": {
@ -10089,9 +10090,10 @@
"license": "MIT"
},
"node_modules/eslint-plugin-sonarjs": {
"version": "0.24.0",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz",
"integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==",
"dev": true,
"license": "LGPL-3.0-only",
"engines": {
"node": ">=16"
},
@ -11234,16 +11236,16 @@
"license": "ISC"
},
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
"minipass": "^7.0.4",
"path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@ -11271,6 +11273,15 @@
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/glob/node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/globals": {
"version": "11.12.0",
"dev": true,
@ -14569,11 +14580,12 @@
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "1.10.1",
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@ -14585,8 +14597,9 @@
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
"dev": true,
"license": "ISC",
"engines": {
"node": "14 || >=16.14"
}

View File

@ -14,8 +14,8 @@
"build": "run-s build-locales esbuild:build",
"build-proxy": "run-s build-locales esbuild:build-proxy",
"watch": "run-s build-locales esbuild:watch",
"lint": "eslint . --max-warnings 0 --fix",
"lint:precommit": "node scripts/eslint-precommit.mjs",
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=8192' eslint . --max-warnings 0 --fix",
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=8192' node scripts/eslint-precommit.mjs",
"lint:spelling": "node scripts/check-spelling.mjs",
"lit-analyse": "lit-analyzer src",
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
@ -32,13 +32,13 @@
"dependencies": {
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.4",
"@codemirror/lang-python": "^6.1.5",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.3.3",
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.5",
"@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2024.2.2-1711369360",
"@goauthentik/api": "^2024.2.2-1711643691",
"@lit-labs/task": "^3.1.0",
"@lit/context": "^1.1.0",
"@lit/localize": "^0.12.1",
@ -46,7 +46,7 @@
"@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.108.0",
"@sentry/browser": "^7.109.0",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"chart.js": "^4.4.2",
@ -80,7 +80,7 @@
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.15",
"@spotlightjs/spotlight": "^1.2.16",
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
"@storybook/api": "^7.6.17",
@ -105,10 +105,10 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-sonarjs": "^0.25.0",
"eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0",
"glob": "^10.3.10",
"glob": "^10.3.12",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
@ -129,9 +129,9 @@
"@esbuild/darwin-arm64": "^0.20.1",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.20.1",
"@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0"
"@rollup/rollup-darwin-arm64": "4.13.2",
"@rollup/rollup-linux-arm64-gnu": "4.13.2",
"@rollup/rollup-linux-x64-gnu": "4.13.2"
},
"engines": {
"node": ">=20"

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
@ -83,28 +84,7 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal">
${(this.result?.logMessages || []).length > 0
? this.result?.logMessages?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer>
</dl>
</div>
</div>

View File

@ -23,13 +23,15 @@ export class BoundStagesList extends Table<FlowStageBinding> {
checkbox = true;
clearOnRefresh = true;
order = "order";
@property()
target?: string;
async apiEndpoint(page: number): Promise<PaginatedResponse<FlowStageBinding>> {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
target: this.target || "",
ordering: "order",
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
});
@ -37,8 +39,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
columns(): TableColumn[] {
return [
new TableColumn(msg("Order")),
new TableColumn(msg("Name")),
new TableColumn(msg("Order"), "order"),
new TableColumn(msg("Name"), "stage__name"),
new TableColumn(msg("Type")),
new TableColumn(msg("Actions")),
];

View File

@ -1,6 +1,7 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -55,28 +56,7 @@ export class FlowImportForm extends Form<Flow> {
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal">
${(this.result?.logs || []).length > 0
? this.result?.logs?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
<ak-log-viewer .logs=${this.result?.logs}></ak-log-viewer>
</dl>
</div>
</div>

View File

@ -31,10 +31,12 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
checkbox = true;
clearOnRefresh = true;
order = "order";
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
target: this.target || "",
ordering: "order",
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
});

View File

@ -3,6 +3,7 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
@ -85,28 +86,7 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal">
${(this.result?.logMessages || []).length > 0
? this.result?.logMessages?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer>
</dl>
</div>
</div>

View File

@ -5,6 +5,7 @@ import { getRelativeTime } from "@goauthentik/common/utils";
import { PFColor } from "@goauthentik/elements/Label";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/events/LogViewer";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
@ -95,9 +96,7 @@ export class SystemTaskListPage extends TablePage<SystemTask> {
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.messages.map((m) => {
return html`<li>${m}</li>`;
})}
<ak-log-viewer .logs=${item?.messages}></ak-log-viewer>
</div>
</dd>
</div>

View File

@ -3,7 +3,7 @@ import {
EventMiddleware,
LoggingMiddleware,
} from "@goauthentik/common/api/middleware";
import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants";
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
@ -86,13 +86,4 @@ export function AndNext(url: string): string {
return `?next=${encodeURIComponent(url)}`;
}
window.addEventListener(EVENT_REFRESH, () => {
// Upon global refresh, disregard whatever was pre-hydrated and
// actually load info from API
globalConfigPromise = undefined;
globalBrandPromise = undefined;
config();
brand();
});
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);

View File

@ -16,7 +16,7 @@ export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
if (!(error instanceof ResponseError)) {
return error;
}
if (error.response.status < 400 && error.response.status > 499) {
if (error.response.status < 400 || error.response.status > 499) {
return error;
}
const body = await error.response.json();

View File

@ -55,9 +55,7 @@ export class PlexAPIClient {
): Promise<{ authUrl: string; pin: PlexPinResponse }> {
const headers = {
...DEFAULT_HEADERS,
...{
"X-Plex-Client-Identifier": clientIdentifier,
},
"X-Plex-Client-Identifier": clientIdentifier,
};
const pinResponse = await fetch("https://plex.tv/api/v2/pins.json?strong=true", {
method: "POST",
@ -75,9 +73,7 @@ export class PlexAPIClient {
static async pinStatus(clientIdentifier: string, id: number): Promise<string | undefined> {
const headers = {
...DEFAULT_HEADERS,
...{
"X-Plex-Client-Identifier": clientIdentifier,
},
"X-Plex-Client-Identifier": clientIdentifier,
};
const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, {
headers: headers,

View File

@ -123,7 +123,7 @@ const isCSSResult = (v: unknown): v is CSSResult =>
// prettier-ignore
export const _adaptCSS = (sheet: AdaptableStylesheet): CSSStyleSheet =>
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, ...[]).styleSheet
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, []).styleSheet
: isCSSResult(sheet) ? sheet.styleSheet
: sheet) as CSSStyleSheet;

View File

@ -1,9 +1,11 @@
import { createContext } from "@lit/context";
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
import type { Config, CurrentBrand, LicenseSummary, SessionUser } from "@goauthentik/api";
export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));
export const authentikUserContext = createContext<SessionUser>(Symbol("authentik-user-context"));
export const authentikEnterpriseContext = createContext<LicenseSummary>(
Symbol("authentik-enterprise-context"),
);

View File

@ -0,0 +1,52 @@
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";
import type { CurrentBrand } from "@goauthentik/api";
import { CoreApi } from "@goauthentik/api";
import type { AkInterface } from "./Interface";
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
export class BrandContextController implements ReactiveController {
host!: ReactiveElementHost;
context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikBrandContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}
fetch() {
new CoreApi(DEFAULT_CONFIG).coreBrandsCurrentRetrieve().then((brand) => {
this.context.setValue(brand);
this.host.brand = brand;
});
}
hostConnected() {
window.addEventListener(EVENT_REFRESH, this.fetch);
}
hostDisconnected() {
window.removeEventListener(EVENT_REFRESH, this.fetch);
}
hostUpdate() {
// If the Interface changes its brand information for some reason,
// we should notify all users of the context of that change. doesn't
if (this.host.brand !== this.context.value) {
this.context.setValue(this.host.brand);
}
}
}

View File

@ -0,0 +1,53 @@
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";
import type { Config } from "@goauthentik/api";
import { RootApi } from "@goauthentik/api";
import type { AkInterface } from "./Interface";
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
export class ConfigContextController implements ReactiveController {
host!: ReactiveElementHost;
context!: ContextProvider<{ __context__: Config | undefined }>;
constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikConfigContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}
fetch() {
new RootApi(DEFAULT_CONFIG).rootConfigRetrieve().then((config) => {
this.context.setValue(config);
this.host.config = config;
});
}
hostConnected() {
window.addEventListener(EVENT_REFRESH, this.fetch);
}
hostDisconnected() {
window.removeEventListener(EVENT_REFRESH, this.fetch);
}
hostUpdate() {
// If the Interface changes its config information, we should notify all
// users of the context of that change, without creating an infinite
// loop of resets.
if (this.host.config !== this.context.value) {
this.context.setValue(this.host.config);
}
}
}

View File

@ -0,0 +1,53 @@
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";
import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";
import type { LicenseSummary } from "@goauthentik/api";
import { EnterpriseApi } from "@goauthentik/api";
import type { AkEnterpriseInterface } from "./Interface";
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkEnterpriseInterface;
export class EnterpriseContextController implements ReactiveController {
host!: ReactiveElementHost;
context!: ContextProvider<{ __context__: LicenseSummary | undefined }>;
constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikEnterpriseContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}
fetch() {
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => {
this.context.setValue(enterprise);
this.host.licenseSummary = enterprise;
});
}
hostConnected() {
window.addEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
}
hostDisconnected() {
window.removeEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
}
hostUpdate() {
// If the Interface changes its config information, we should notify all
// users of the context of that change, without creating an infinite
// loop of resets.
if (this.host.licenseSummary !== this.context.value) {
this.context.setValue(this.host.licenseSummary);
}
}
}

View File

@ -1,77 +1,47 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { brand, config } from "@goauthentik/common/api/config";
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
import {
authentikBrandContext,
authentikConfigContext,
authentikEnterpriseContext,
} from "@goauthentik/elements/AuthentikContexts";
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
import { ContextProvider } from "@lit/context";
import { state } from "lit/decorators.js";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
import { EnterpriseApi, UiThemeEnum } from "@goauthentik/api";
import { UiThemeEnum } from "@goauthentik/api";
import { AKElement } from "../Base";
import { BrandContextController } from "./BrandContextController";
import { ConfigContextController } from "./ConfigContextController";
import { EnterpriseContextController } from "./EnterpriseContextController";
type AkInterface = HTMLElement & {
export type AkInterface = HTMLElement & {
getTheme: () => Promise<UiThemeEnum>;
brand?: CurrentBrand;
uiConfig?: UIConfig;
config?: Config;
};
const brandContext = Symbol("brandContext");
const configContext = Symbol("configContext");
export class Interface extends AKElement implements AkInterface {
@state()
uiConfig?: UIConfig;
_configContext = new ContextProvider(this, {
context: authentikConfigContext,
initialValue: undefined,
});
[brandContext]!: BrandContextController;
_config?: Config;
[configContext]!: ConfigContextController;
@state()
set config(c: Config) {
this._config = c;
this._configContext.setValue(c);
this.requestUpdate();
}
get config(): Config | undefined {
return this._config;
}
_brandContext = new ContextProvider(this, {
context: authentikBrandContext,
initialValue: undefined,
});
_brand?: CurrentBrand;
config?: Config;
@state()
set brand(c: CurrentBrand) {
this._brand = c;
this._brandContext.setValue(c);
this.requestUpdate();
}
get brand(): CurrentBrand | undefined {
return this._brand;
}
brand?: CurrentBrand;
constructor() {
super();
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
brand().then((brand) => (this.brand = brand));
config().then((config) => (this.config = config));
this[brandContext] = new BrandContextController(this);
this[configContext] = new ConfigContextController(this);
this.dataset.akInterfaceRoot = "true";
}
@ -88,37 +58,20 @@ export class Interface extends AKElement implements AkInterface {
}
}
export class EnterpriseAwareInterface extends Interface {
_licenseSummaryContext = new ContextProvider(this, {
context: authentikEnterpriseContext,
initialValue: undefined,
});
export type AkEnterpriseInterface = AkInterface & {
licenseSummary?: LicenseSummary;
};
_licenseSummary?: LicenseSummary;
const enterpriseContext = Symbol("enterpriseContext");
export class EnterpriseAwareInterface extends Interface {
[enterpriseContext]!: EnterpriseContextController;
@state()
set licenseSummary(c: LicenseSummary) {
this._licenseSummary = c;
this._licenseSummaryContext.setValue(c);
this.requestUpdate();
}
get licenseSummary(): LicenseSummary | undefined {
return this._licenseSummary;
}
licenseSummary?: LicenseSummary;
constructor() {
super();
const refreshStatus = () => {
new EnterpriseApi(DEFAULT_CONFIG)
.enterpriseLicenseSummaryRetrieve()
.then((enterprise) => {
this.licenseSummary = enterprise;
});
};
refreshStatus();
window.addEventListener(EVENT_REFRESH_ENTERPRISE, () => {
refreshStatus();
});
this[enterpriseContext] = new EnterpriseContextController(this);
}
}

View File

@ -56,7 +56,7 @@ export class Markdown extends AKElement {
converter = new showdown.Converter({ metadata: true, tables: true });
replaceAdmonitions(input: string): string {
const admonitionStart = /:::(\w+)<br\s\/>/gm;
const admonitionStart = /:::(\w+)(<br\s*\/>|\s*$)/gm;
const admonitionEnd = /:::/gm;
return (
input

View File

@ -12,7 +12,7 @@ import AKTokenCopyButton from "./ak-token-copy-button";
function makeid(length: number) {
const sample = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new Array(length)
return Array.from({ length })
.fill(" ")
.map(() => sample.charAt(Math.floor(Math.random() * sample.length)))
.join("");

View File

@ -0,0 +1,114 @@
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/EmptyState";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { LogEvent, LogLevelEnum } from "@goauthentik/api";
@customElement("ak-log-viewer")
export class LogViewer extends Table<LogEvent> {
@property({ attribute: false })
logs?: LogEvent[] = [];
expandable = true;
paginated = false;
static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList);
}
async apiEndpoint(_page: number): Promise<PaginatedResponse<LogEvent>> {
return {
pagination: {
next: 0,
previous: 0,
count: 1,
current: 1,
totalPages: 1,
startIndex: 1,
endIndex: 1,
},
results: this.logs || [],
};
}
renderEmpty(): TemplateResult {
return super.renderEmpty(
html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
);
}
renderExpanded(item: LogEvent): TemplateResult {
return html`<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Timestamp")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.timestamp.toLocaleString()}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Attributes")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<pre>${JSON.stringify(item.attributes, null, 4)}</pre>
</div>
</dd>
</div>
</dl>
</div>
</td>`;
}
renderToolbarContainer(): TemplateResult {
return html``;
}
columns(): TableColumn[] {
return [
new TableColumn(msg("Time")),
new TableColumn(msg("Level")),
new TableColumn(msg("Event")),
new TableColumn(msg("Logger")),
];
}
statusForItem(item: LogEvent): string {
switch (item.logLevel) {
case LogLevelEnum.Critical:
case LogLevelEnum.Error:
case LogLevelEnum.Exception:
return "error";
case LogLevelEnum.Warn:
case LogLevelEnum.Warning:
return "warning";
default:
return "info";
}
}
row(item: LogEvent): TemplateResult[] {
return [
html`${getRelativeTime(item.timestamp)}`,
html`<ak-status-label
type=${this.statusForItem(item)}
bad-label=${item.logLevel}
></ak-status-label>`,
html`${item.event}`,
html`${item.logger}`,
];
}
}

View File

@ -91,7 +91,7 @@ export class RouterOutlet extends AKElement {
let matchedRoute: RouteMatch | null = null;
this.routes.some((route) => {
const match = route.url.exec(activeUrl);
if (match != null) {
if (match !== null) {
matchedRoute = new RouteMatch(route);
matchedRoute.arguments = match.groups || {};
matchedRoute.fullUrl = activeUrl;

View File

@ -1,5 +1,4 @@
import { docLink } from "@goauthentik/common/global";
import { adaptCSS } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
@ -20,23 +19,23 @@ import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
* administrator, provide a link to the "Create a new application" page.
*/
const styles = adaptCSS([
PFBase,
PFEmptyState,
PFButton,
PFContent,
PFSpacing,
css`
.cta {
display: inline-block;
font-weight: bold;
}
`,
]);
@customElement("ak-library-application-empty-list")
export class LibraryPageApplicationEmptyList extends AKElement {
static styles = styles;
static get styles() {
return [
PFBase,
PFEmptyState,
PFButton,
PFContent,
PFSpacing,
css`
.cta {
display: inline-block;
font-weight: bold;
}
`,
];
}
@property({ attribute: "isadmin", type: Boolean })
isAdmin = false;

View File

@ -31,22 +31,22 @@ const LAYOUTS = new Map<string, [string, string]>([
],
]);
const styles = [
PFBase,
PFEmptyState,
PFContent,
PFGrid,
css`
.app-group-header {
margin-bottom: 1em;
margin-top: 1.2em;
}
`,
];
@customElement("ak-library-application-list")
export class LibraryPageApplicationList extends AKElement {
static styles = styles;
static get styles() {
return [
PFBase,
PFEmptyState,
PFContent,
PFGrid,
css`
.app-group-header {
margin-bottom: 1em;
margin-top: 1.2em;
}
`,
];
}
@property({ attribute: true })
layout = "row" as LayoutType;

View File

@ -18,24 +18,26 @@ import { customEvent } from "./helpers";
@customElement("ak-library-list-search")
export class LibraryPageApplicationList extends AKElement {
static styles = [
PFBase,
PFDisplay,
css`
input {
width: 30ch;
box-sizing: border-box;
border: 0;
border-bottom: 1px solid;
border-bottom-color: var(--ak-accent);
background-color: transparent;
font-size: 1.5rem;
}
input:focus {
outline: 0;
}
`,
];
static get styles() {
return [
PFBase,
PFDisplay,
css`
input {
width: 30ch;
box-sizing: border-box;
border: 0;
border-bottom: 1px solid;
border-bottom-color: var(--ak-accent);
background-color: transparent;
font-size: 1.5rem;
}
input:focus {
outline: 0;
}
`,
];
}
@property({ attribute: false })
set apps(value: Application[]) {

View File

@ -35,7 +35,9 @@ import type { AppGroupList, PageUIConfig } from "./types";
@customElement("ak-library-impl")
export class LibraryPage extends AKElement {
static styles = styles;
static get styles() {
return styles;
}
@property({ attribute: "isadmin", type: Boolean })
isAdmin = false;

54
web/tsconfig.base.json Normal file
View File

@ -0,0 +1,54 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"esModuleInterop": true,
"paths": {
"@goauthentik/docs/*": ["../website/docs/*"]
},
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
],
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"useDefineForClassFields": false,
"alwaysStrict": true,
"noImplicitAny": true,
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-unknown-tag-name": "off",
"no-missing-import": "off",
"no-incompatible-type-binding": "off",
"no-unknown-property": "off",
"no-unknown-attribute": "off"
}
}
]
}
}

View File

@ -1,6 +1,6 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"strict": true,
"paths": {
"@goauthentik/authentik/*": ["src/*"],
"@goauthentik/admin/*": ["src/admin/*"],
@ -14,51 +14,5 @@
"@goauthentik/standalone/*": ["src/standalone/*"],
"@goauthentik/user/*": ["src/user/*"]
},
"baseUrl": ".",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
],
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"useDefineForClassFields": false,
"alwaysStrict": true,
"noImplicitAny": true,
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-unknown-tag-name": "off",
"no-missing-import": "off",
"no-incompatible-type-binding": "off",
"no-unknown-property": "off",
"no-unknown-attribute": "off"
}
}
]
}
}

View File

@ -27,7 +27,7 @@ Starting in 2021.9, you can also select a Notification mapping. This allows you
```python
return {
"foo": context['notification'].body,
"foo": request.context['notification'].body,
}
```

View File

@ -1,39 +0,0 @@
---
title: Air-gapped environments
---
## Outbound connections
By default, authentik creates outbound connections to the following URLs:
- https://version.goauthentik.io: Periodic update check
- https://goauthentik.io: Anonymous analytics on startup
- https://secure.gravatar.com: Avatars for users
- https://authentik.error-reporting.a7k.io: Error reporting
To disable these outbound connections, set the following in your `.env` file:
```
AUTHENTIK_DISABLE_UPDATE_CHECK=true
AUTHENTIK_ERROR_REPORTING__ENABLED=false
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
AUTHENTIK_AVATARS=initials
```
For a Helm-based install, set the following in your values.yaml file:
```yaml
authentik:
avatars: none
error_reporting:
enabled: false
disable_update_check: true
disable_startup_analytics: true
```
## Container images
Container images can be pulled from the following URLs:
- ghcr.io/goauthentik/server (https://ghcr.io)
- beryju/authentik (https://index.docker.io)

View File

@ -0,0 +1,68 @@
---
title: Air-gapped environments
---
## Outbound connections
By default, authentik creates outbound connections to the following URLs:
- https://version.goauthentik.io: Periodic update check
- https://goauthentik.io: Anonymous analytics on startup
- https://secure.gravatar.com: Avatars for users
- https://authentik.error-reporting.a7k.io: Error reporting
To disable these outbound connections, set the following in your `.env` file:
## Configuration options
To see a list of all configuration options, see [here](./configuration.mdx).
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
<Tabs
defaultValue="docker-compose"
values={[
{label: 'docker-compose', value: 'docker-compose'},
{label: 'Kubernetes', value: 'kubernetes'},
]}>
<TabItem value="docker-compose">
Add the following block to your `.env` file:
```shell
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
AUTHENTIK_DISABLE_UPDATE_CHECK=true
AUTHENTIK_ERROR_REPORTING__ENABLED=false
```
Afterwards, run the upgrade commands from the latest release notes.
</TabItem>
<TabItem value="kubernetes">
Add the following block to your `values.yml` file:
```yaml
authentik:
error_reporting:
enabled: false
disable_update_check: true
disable_startup_analytics: true
```
Afterwards, run the upgrade commands from the latest release notes.
</TabItem>
</Tabs>
## Settings
In addition to the configuration options above, the following [System settings](../core/settings.md) need to also be adjusted:
- **Avatars**: By default this setting uses [Gravatar](https://secure.gravatar.com/). The option can be set to a combination of any of the other options, for example `initials`
## Container images
Container images can be pulled from the following URLs:
- ghcr.io/goauthentik/server (https://ghcr.io)
- beryju/authentik (https://index.docker.io)

View File

@ -32,7 +32,10 @@ The following placeholders will be used:
- **Redirect URIs/Origins (RegEx)**:
:::note
Please note that the following URIs are just examples. Be sure to include all of the domains / URLs that you will use to access Immich.
::: - app.immich:/ - https://immich.company/auth/login - https://immich.company/user-settings
:::
- app.immich:/
- https://immich.company/auth/login
- https://immich.company/user-settings
- **Signing Key**: authentik Self-signed Certificate
- Leave everything else as default
2. Open the new provider you've just created.

View File

@ -49,8 +49,8 @@ environment: OAUTH2_ENABLED=true
OAUTH2_USERINFO_ENDPOINT=/application/o/userinfo/
OAUTH2_TOKEN_ENDPOINT=/application/o/token/
OAUTH2_SECRET=<Client Secret from above>
OAUTH2_ID_MAP=preferred_username
OAUTH2_USERNAME_MAP=preferred_username
OAUTH2_ID_MAP=sub
OAUTH2_USERNAME_MAP=email
OAUTH2_FULLNAME_MAP=given_name
OAUTH2_EMAIL_MAP=email
```
@ -70,8 +70,8 @@ edit `.env` and add the following:
OAUTH2_USERINFO_ENDPOINT='/application/o/userinfo/'
OAUTH2_TOKEN_ENDPOINT='/application/o/token/'
OAUTH2_SECRET='<Client Secret from above>'
OAUTH2_ID_MAP='preferred_username'
OAUTH2_USERNAME_MAP='preferred_username'
OAUTH2_ID_MAP='sub'
OAUTH2_USERNAME_MAP='email'
OAUTH2_FULLNAME_MAP='given_name'
OAUTH2_EMAIL_MAP='email'
```

View File

@ -0,0 +1,69 @@
---
title: Xen Orchestra
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Xen Orchestra
> Xen Orchestra provides a user friendly web interface for every Xen based hypervisor (XenServer, xcp-ng, etc.).
>
> -- https://xen-orchestra.com/
:::note
Xen Orchestra offers authentication plugins for OpenID Connect, SAML and LDAP. This guide is using the OpenID Connect plugin.
If you are using the Xen Orchestra Appliance, the OIDC Plugin should be present. If you are using Xen Orchestra compiled from sources, make sure the plugin `auth-oidc` is installed.
:::
## Preparation
The following placeholders will be used:
- `xenorchestra.company` is the FQDN of the Xen Orchestra instance.
- `authentik.company` is the FQDN of the authentik install.
## authentik configuration
### 1. Provider
Under _Providers_, create an OAuth2/OpenID provider with these settings:
- Name: Provider for XenOrchestra
- Authorization Flow: Select one of the available Flows.
- Client type: Confidential
- Redirect URIs/Origins: `https://xenorchestra.company/signin/oidc/callback`
Take note of the Client ID and the Client Secret, because we need them for the configuration of Xen Orchestra.
### 2. Application
Create an application with the following details:
- Slug: `xenorchestra` (If you want to choose a different slug, your URLs for the Xen Orchestra Configuration may vary.)
- Provider: Select the one we have created in Step 1
- Set the Launch URL to `https://xenorchestra.company/`
Optionally apply access restrictions to the application.
## Xen Orchestra configuration
Xen Orchestra allows the configuration of the OpenID Connect authentication in the plugin-section.
All of the URLs mentioned below can be copied & pasted from authentik (_Applications -> Providers -> *the provider created earlier*_).
1. Navigate to Settings -> Plugins
2. Scroll to **auth-oidc** and click on the **+** icon on the right hand side.
3. Configure the auth-oidc plugin with the following configuration values:
- Set the `Auto-discovery URL` to `https://authentik.company/application/o/xenorchestra/.well-known/openid-configuration`.
- Set the `Client identifier (key)` to the Client ID from your notes.
- Set the `Client secret` to the Client Secret from your notes.
- Check the `Fill information (optional)`-Checkbox to open the advanced menu.
- Set the `Username field` to `username`
- Set the `Scopes` to `openid profile email`
4. Enable the `auth-oidc`-Plugin by toggling the switch above the configuration.
5. You should be able to login with OIDC.
:::note
The first time a user signs in, Xen Orchesta will create a new user with the same username used in authentik. If you want to map the users by their e-mail-address instead of their username, you have to set the `Username field` to `email` in the Xen Orchestra plugin configuration.
:::

View File

@ -33,7 +33,7 @@
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/tsconfig": "3.1.1",
"@docusaurus/types": "3.1.1",
"@types/react": "^18.2.70",
"@types/react": "^18.2.73",
"prettier": "3.2.5",
"typescript": "~5.4.3"
},
@ -3999,12 +3999,11 @@
"integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA=="
},
"node_modules/@types/react": {
"version": "18.2.70",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.70.tgz",
"integrity": "sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==",
"version": "18.2.73",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.73.tgz",
"integrity": "sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
@ -4050,11 +4049,6 @@
"@types/node": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.5",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz",
"integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw=="
},
"node_modules/@types/send": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz",

View File

@ -52,7 +52,7 @@
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/tsconfig": "3.1.1",
"@docusaurus/types": "3.1.1",
"@types/react": "^18.2.70",
"@types/react": "^18.2.73",
"prettier": "3.2.5",
"typescript": "~5.4.3"
},

View File

@ -63,6 +63,7 @@ module.exports = {
"services/portainer/index",
"services/proxmox-ve/index",
"services/rancher/index",
"services/xen-orchestra/index",
"services/vmware-vcenter/index",
],
},