mirror of
https://github.com/goauthentik/authentik
synced 2026-04-26 01:25:02 +02:00
Compare commits
116 Commits
admin/vers
...
oauth_back
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65c7f784b3 | ||
|
|
e5acaa2603 | ||
|
|
bb2ef38ecd | ||
|
|
5364c49068 | ||
|
|
06d4f17743 | ||
|
|
9153cfe83c | ||
|
|
709cf785a9 | ||
|
|
db70211754 | ||
|
|
31510bb276 | ||
|
|
4bc6ba3dbe | ||
|
|
125af7daf5 | ||
|
|
130d0235d4 | ||
|
|
6ba97cba5a | ||
|
|
7aac9dea53 | ||
|
|
3f41588dc9 | ||
|
|
9caf08637e | ||
|
|
3f2f5ba31a | ||
|
|
ca48a6de48 | ||
|
|
6c951efd61 | ||
|
|
6b5da70673 | ||
|
|
32501266d5 | ||
|
|
5d6a2d27eb | ||
|
|
7b8426b292 | ||
|
|
fd8d5952d5 | ||
|
|
bb1e16a3f8 | ||
|
|
09a4f2c9b6 | ||
|
|
ac2873e6f4 | ||
|
|
6d68844270 | ||
|
|
2a65579182 | ||
|
|
11203f79d1 | ||
|
|
b670362168 | ||
|
|
24ae1c8b03 | ||
|
|
7a4c6b9b50 | ||
|
|
f1684882c5 | ||
|
|
615cb4f9fb | ||
|
|
bbfee74cd2 | ||
|
|
af2885ab12 | ||
|
|
29799e95ce | ||
|
|
b6568f322b | ||
|
|
d9bad736b5 | ||
|
|
1d57307891 | ||
|
|
be27635788 | ||
|
|
8373072654 | ||
|
|
b6c05f6852 | ||
|
|
26766360d5 | ||
|
|
9d7c733024 | ||
|
|
caecf5961d | ||
|
|
4b211190b6 | ||
|
|
2f22012f0f | ||
|
|
10dffd8d13 | ||
|
|
21a73fe58c | ||
|
|
b0b915061e | ||
|
|
6bcb758daa | ||
|
|
b051c59ec4 | ||
|
|
35df455e3a | ||
|
|
eb19e53bf3 | ||
|
|
3badf80295 | ||
|
|
b038e479e2 | ||
|
|
faaf4842a5 | ||
|
|
89ef57c68d | ||
|
|
c3515299d1 | ||
|
|
f8ff48fed9 | ||
|
|
dcf40690e9 | ||
|
|
4b37829f67 | ||
|
|
bf050e19b0 | ||
|
|
8eedfe5c4e | ||
|
|
33f83bec46 | ||
|
|
b93a450b38 | ||
|
|
6c169ce6a6 | ||
|
|
d53bb73c91 | ||
|
|
a182d7671e | ||
|
|
9941cec71f | ||
|
|
458344638f | ||
|
|
11ffd672ca | ||
|
|
4ebbf5b097 | ||
|
|
1bf5688ec6 | ||
|
|
9f1e6b3ba4 | ||
|
|
93ec4b3c17 | ||
|
|
477fc11148 | ||
|
|
4265e7b0af | ||
|
|
41df11d5dc | ||
|
|
8ba469496c | ||
|
|
2bdcadda44 | ||
|
|
6ff697de46 | ||
|
|
7594e2ba56 | ||
|
|
66587bbe69 | ||
|
|
3711c6ede4 | ||
|
|
b422714a0b | ||
|
|
158ee1d7cf | ||
|
|
13afc01e21 | ||
|
|
ec90666c9d | ||
|
|
04cda27496 | ||
|
|
40575c7c71 | ||
|
|
3b37221fd8 | ||
|
|
0dc8062611 | ||
|
|
b81dc63ad6 | ||
|
|
d0534ca55f | ||
|
|
1f1e55a36d | ||
|
|
8989593356 | ||
|
|
eaf4e46eb6 | ||
|
|
1b401668ec | ||
|
|
bc10f869d2 | ||
|
|
d217f2f1fe | ||
|
|
6bbebbac4c | ||
|
|
345756024e | ||
|
|
e7a404c4d3 | ||
|
|
4244263c29 | ||
|
|
e3c8e613fb | ||
|
|
0b32da1e48 | ||
|
|
31b313d937 | ||
|
|
f374fb5651 | ||
|
|
bc8556c6b4 | ||
|
|
effb3ff910 | ||
|
|
7e23a93846 | ||
|
|
5ea8bbf269 | ||
|
|
014e22c3d0 |
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.6.3
|
||||
current_version = 2025.6.4
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -7,6 +7,8 @@
|
||||
"!Enumerate sequence",
|
||||
"!Env scalar",
|
||||
"!Env sequence",
|
||||
"!File scalar",
|
||||
"!File sequence",
|
||||
"!Find sequence",
|
||||
"!Format sequence",
|
||||
"!If sequence",
|
||||
|
||||
@@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.21 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.2 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||
|
||||
|
||||
6
Makefile
6
Makefile
@@ -243,10 +243,12 @@ docs-build:
|
||||
docs-watch: ## Build and watch the topics documentation
|
||||
npm run start --prefix website
|
||||
|
||||
docs-integrations-build:
|
||||
integrations: docs-lint-fix integrations-build ## Fix formatting issues in the integrations source code, lint the code, and compile it
|
||||
|
||||
integrations-build:
|
||||
npm run build --prefix website -w integrations
|
||||
|
||||
docs-integrations-watch: ## Build and watch the Integrations documentation
|
||||
integrations-watch: ## Build and watch the Integrations documentation
|
||||
npm run start --prefix website -w integrations
|
||||
|
||||
#########################
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.6.3"
|
||||
__version__ = "2025.6.4"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
||||
43
authentik/blueprints/tests/fixtures/tags.yaml
vendored
43
authentik/blueprints/tests/fixtures/tags.yaml
vendored
@@ -12,8 +12,8 @@ context:
|
||||
context1: context-nested-value
|
||||
context2: !Context context1
|
||||
entries:
|
||||
- model: !Format ["%s", authentik_sources_oauth.oauthsource]
|
||||
state: !Format ["%s", present]
|
||||
- model: !Format ["%%s", authentik_sources_oauth.oauthsource]
|
||||
state: !Format ["%%s", present]
|
||||
identifiers:
|
||||
slug: test
|
||||
attrs:
|
||||
@@ -27,20 +27,23 @@ entries:
|
||||
[slug, default-source-authentication],
|
||||
]
|
||||
enrollment_flow:
|
||||
!Find [!Format ["%s", authentik_flows.Flow], [slug, default-source-enrollment]]
|
||||
!Find [!Format ["%%s", authentik_flows.Flow], [slug, default-source-enrollment]]
|
||||
- attrs:
|
||||
expression: return True
|
||||
identifiers:
|
||||
name: !Format [foo-%s-%s-%s, !Context foo, !Context bar, qux]
|
||||
name: !Format [foo-%%s-%%s-%%s, !Context foo, !Context bar, qux]
|
||||
id: policy
|
||||
model: authentik_policies_expression.expressionpolicy
|
||||
- attrs:
|
||||
attributes:
|
||||
env_null: !Env [bar-baz, null]
|
||||
file_content: !File '%(file_name)s'
|
||||
file_default: !File ['%(file_default_name)s', 'default']
|
||||
file_non_existent: !File '/does-not-exist'
|
||||
json_parse: !ParseJSON '{"foo": "bar"}'
|
||||
policy_pk1:
|
||||
!Format [
|
||||
"%s-%s",
|
||||
"%%s-%%s",
|
||||
!Find [
|
||||
authentik_policies_expression.expressionpolicy,
|
||||
[
|
||||
@@ -51,29 +54,29 @@ entries:
|
||||
],
|
||||
suffix,
|
||||
]
|
||||
policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix]
|
||||
policy_pk2: !Format ["%%s-%%s", !KeyOf policy, suffix]
|
||||
boolAnd:
|
||||
!Condition [AND, !Context foo, !Format ["%s", "a_string"], 1]
|
||||
!Condition [AND, !Context foo, !Format ["%%s", "a_string"], 1]
|
||||
boolNand:
|
||||
!Condition [NAND, !Context foo, !Format ["%s", "a_string"], 1]
|
||||
!Condition [NAND, !Context foo, !Format ["%%s", "a_string"], 1]
|
||||
boolOr:
|
||||
!Condition [
|
||||
OR,
|
||||
!Context foo,
|
||||
!Format ["%s", "a_string"],
|
||||
!Format ["%%s", "a_string"],
|
||||
null,
|
||||
]
|
||||
boolNor:
|
||||
!Condition [
|
||||
NOR,
|
||||
!Context foo,
|
||||
!Format ["%s", "a_string"],
|
||||
!Format ["%%s", "a_string"],
|
||||
null,
|
||||
]
|
||||
boolXor:
|
||||
!Condition [XOR, !Context foo, !Format ["%s", "a_string"], 1]
|
||||
!Condition [XOR, !Context foo, !Format ["%%s", "a_string"], 1]
|
||||
boolXnor:
|
||||
!Condition [XNOR, !Context foo, !Format ["%s", "a_string"], 1]
|
||||
!Condition [XNOR, !Context foo, !Format ["%%s", "a_string"], 1]
|
||||
boolComplex:
|
||||
!Condition [
|
||||
XNOR,
|
||||
@@ -89,7 +92,7 @@ entries:
|
||||
{
|
||||
with: { keys: "and_values" },
|
||||
and_nested_custom_tags:
|
||||
!Format ["foo-%s", !Context foo],
|
||||
!Format ["foo-%%s", !Context foo],
|
||||
},
|
||||
},
|
||||
null,
|
||||
@@ -98,7 +101,7 @@ entries:
|
||||
!If [
|
||||
!Condition [AND, false],
|
||||
null,
|
||||
[list, with, items, !Format ["foo-%s", !Context foo]],
|
||||
[list, with, items, !Format ["foo-%%s", !Context foo]],
|
||||
]
|
||||
if_true_simple: !If [!Context foo, true, text]
|
||||
if_short: !If [!Context foo]
|
||||
@@ -106,22 +109,22 @@ entries:
|
||||
enumerate_mapping_to_mapping: !Enumerate [
|
||||
!Context mapping,
|
||||
MAP,
|
||||
[!Format ["prefix-%s", !Index 0], !Format ["other-prefix-%s", !Value 0]]
|
||||
[!Format ["prefix-%%s", !Index 0], !Format ["other-prefix-%%s", !Value 0]]
|
||||
]
|
||||
enumerate_mapping_to_sequence: !Enumerate [
|
||||
!Context mapping,
|
||||
SEQ,
|
||||
!Format ["prefixed-pair-%s-%s", !Index 0, !Value 0]
|
||||
!Format ["prefixed-pair-%%s-%%s", !Index 0, !Value 0]
|
||||
]
|
||||
enumerate_sequence_to_sequence: !Enumerate [
|
||||
!Context sequence,
|
||||
SEQ,
|
||||
!Format ["prefixed-items-%s-%s", !Index 0, !Value 0]
|
||||
!Format ["prefixed-items-%%s-%%s", !Index 0, !Value 0]
|
||||
]
|
||||
enumerate_sequence_to_mapping: !Enumerate [
|
||||
!Context sequence,
|
||||
MAP,
|
||||
[!Format ["index: %d", !Index 0], !Value 0]
|
||||
[!Format ["index: %%d", !Index 0], !Value 0]
|
||||
]
|
||||
nested_complex_enumeration: !Enumerate [
|
||||
!Context sequence,
|
||||
@@ -132,9 +135,9 @@ entries:
|
||||
!Context mapping,
|
||||
MAP,
|
||||
[
|
||||
!Format ["%s", !Index 0],
|
||||
!Format ["%%s", !Index 0],
|
||||
[
|
||||
!Enumerate [!Value 2, SEQ, !Format ["prefixed-%s", !Value 0]],
|
||||
!Enumerate [!Value 2, SEQ, !Format ["prefixed-%%s", !Value 0]],
|
||||
{
|
||||
outer_value: !Value 1,
|
||||
outer_index: !Index 1,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Test blueprints v1"""
|
||||
|
||||
from os import environ
|
||||
from os import chmod, environ, unlink, write
|
||||
from tempfile import mkstemp
|
||||
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
@@ -131,97 +132,112 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||
ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
|
||||
Group.objects.filter(name="test").delete()
|
||||
environ["foo"] = generate_id()
|
||||
importer = Importer.from_string(load_fixture("fixtures/tags.yaml"), {"bar": "baz"})
|
||||
file, file_name = mkstemp()
|
||||
write(file, b"foo")
|
||||
_, file_default_name = mkstemp()
|
||||
chmod(file_default_name, 0o000) # Remove all permissions so we can't read the file
|
||||
importer = Importer.from_string(
|
||||
load_fixture(
|
||||
"fixtures/tags.yaml",
|
||||
file_name=file_name,
|
||||
file_default_name=file_default_name,
|
||||
),
|
||||
{"bar": "baz"},
|
||||
)
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
|
||||
self.assertTrue(policy)
|
||||
self.assertTrue(
|
||||
Group.objects.filter(
|
||||
attributes={
|
||||
"policy_pk1": str(policy.pk) + "-suffix",
|
||||
"policy_pk2": str(policy.pk) + "-suffix",
|
||||
"boolAnd": True,
|
||||
"boolNand": False,
|
||||
"boolOr": True,
|
||||
"boolNor": False,
|
||||
"boolXor": True,
|
||||
"boolXnor": False,
|
||||
"boolComplex": True,
|
||||
"if_true_complex": {
|
||||
"dictionary": {
|
||||
"with": {"keys": "and_values"},
|
||||
"and_nested_custom_tags": "foo-bar",
|
||||
}
|
||||
group = Group.objects.filter(name="test").first()
|
||||
self.assertIsNotNone(group)
|
||||
self.assertEqual(
|
||||
group.attributes,
|
||||
{
|
||||
"policy_pk1": str(policy.pk) + "-suffix",
|
||||
"policy_pk2": str(policy.pk) + "-suffix",
|
||||
"boolAnd": True,
|
||||
"boolNand": False,
|
||||
"boolOr": True,
|
||||
"boolNor": False,
|
||||
"boolXor": True,
|
||||
"boolXnor": False,
|
||||
"boolComplex": True,
|
||||
"if_true_complex": {
|
||||
"dictionary": {
|
||||
"with": {"keys": "and_values"},
|
||||
"and_nested_custom_tags": "foo-bar",
|
||||
}
|
||||
},
|
||||
"if_false_complex": ["list", "with", "items", "foo-bar"],
|
||||
"if_true_simple": True,
|
||||
"if_short": True,
|
||||
"if_false_simple": 2,
|
||||
"enumerate_mapping_to_mapping": {
|
||||
"prefix-key1": "other-prefix-value",
|
||||
"prefix-key2": "other-prefix-2",
|
||||
},
|
||||
"enumerate_mapping_to_sequence": [
|
||||
"prefixed-pair-key1-value",
|
||||
"prefixed-pair-key2-2",
|
||||
],
|
||||
"enumerate_sequence_to_sequence": [
|
||||
"prefixed-items-0-foo",
|
||||
"prefixed-items-1-bar",
|
||||
],
|
||||
"enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
|
||||
"nested_complex_enumeration": {
|
||||
"0": {
|
||||
"key1": [
|
||||
["prefixed-f", "prefixed-o", "prefixed-o"],
|
||||
{
|
||||
"outer_value": "foo",
|
||||
"outer_index": 0,
|
||||
"middle_value": "value",
|
||||
"middle_index": "key1",
|
||||
},
|
||||
],
|
||||
"key2": [
|
||||
["prefixed-f", "prefixed-o", "prefixed-o"],
|
||||
{
|
||||
"outer_value": "foo",
|
||||
"outer_index": 0,
|
||||
"middle_value": 2,
|
||||
"middle_index": "key2",
|
||||
},
|
||||
],
|
||||
},
|
||||
"if_false_complex": ["list", "with", "items", "foo-bar"],
|
||||
"if_true_simple": True,
|
||||
"if_short": True,
|
||||
"if_false_simple": 2,
|
||||
"enumerate_mapping_to_mapping": {
|
||||
"prefix-key1": "other-prefix-value",
|
||||
"prefix-key2": "other-prefix-2",
|
||||
"1": {
|
||||
"key1": [
|
||||
["prefixed-b", "prefixed-a", "prefixed-r"],
|
||||
{
|
||||
"outer_value": "bar",
|
||||
"outer_index": 1,
|
||||
"middle_value": "value",
|
||||
"middle_index": "key1",
|
||||
},
|
||||
],
|
||||
"key2": [
|
||||
["prefixed-b", "prefixed-a", "prefixed-r"],
|
||||
{
|
||||
"outer_value": "bar",
|
||||
"outer_index": 1,
|
||||
"middle_value": 2,
|
||||
"middle_index": "key2",
|
||||
},
|
||||
],
|
||||
},
|
||||
"enumerate_mapping_to_sequence": [
|
||||
"prefixed-pair-key1-value",
|
||||
"prefixed-pair-key2-2",
|
||||
],
|
||||
"enumerate_sequence_to_sequence": [
|
||||
"prefixed-items-0-foo",
|
||||
"prefixed-items-1-bar",
|
||||
],
|
||||
"enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
|
||||
"nested_complex_enumeration": {
|
||||
"0": {
|
||||
"key1": [
|
||||
["prefixed-f", "prefixed-o", "prefixed-o"],
|
||||
{
|
||||
"outer_value": "foo",
|
||||
"outer_index": 0,
|
||||
"middle_value": "value",
|
||||
"middle_index": "key1",
|
||||
},
|
||||
],
|
||||
"key2": [
|
||||
["prefixed-f", "prefixed-o", "prefixed-o"],
|
||||
{
|
||||
"outer_value": "foo",
|
||||
"outer_index": 0,
|
||||
"middle_value": 2,
|
||||
"middle_index": "key2",
|
||||
},
|
||||
],
|
||||
},
|
||||
"1": {
|
||||
"key1": [
|
||||
["prefixed-b", "prefixed-a", "prefixed-r"],
|
||||
{
|
||||
"outer_value": "bar",
|
||||
"outer_index": 1,
|
||||
"middle_value": "value",
|
||||
"middle_index": "key1",
|
||||
},
|
||||
],
|
||||
"key2": [
|
||||
["prefixed-b", "prefixed-a", "prefixed-r"],
|
||||
{
|
||||
"outer_value": "bar",
|
||||
"outer_index": 1,
|
||||
"middle_value": 2,
|
||||
"middle_index": "key2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"nested_context": "context-nested-value",
|
||||
"env_null": None,
|
||||
"json_parse": {"foo": "bar"},
|
||||
"at_index_sequence": "foo",
|
||||
"at_index_sequence_default": "non existent",
|
||||
"at_index_mapping": 2,
|
||||
"at_index_mapping_default": "non existent",
|
||||
}
|
||||
).exists()
|
||||
},
|
||||
"nested_context": "context-nested-value",
|
||||
"env_null": None,
|
||||
"file_content": "foo",
|
||||
"file_default": "default",
|
||||
"file_non_existent": None,
|
||||
"json_parse": {"foo": "bar"},
|
||||
"at_index_sequence": "foo",
|
||||
"at_index_sequence_default": "non existent",
|
||||
"at_index_mapping": 2,
|
||||
"at_index_mapping_default": "non existent",
|
||||
},
|
||||
)
|
||||
self.assertTrue(
|
||||
OAuthSource.objects.filter(
|
||||
@@ -229,6 +245,8 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||
consumer_key=environ["foo"],
|
||||
)
|
||||
)
|
||||
unlink(file_name)
|
||||
unlink(file_default_name)
|
||||
|
||||
def test_export_validate_import_policies(self):
|
||||
"""Test export and validate it"""
|
||||
|
||||
@@ -18,12 +18,15 @@ from django.db.models import Model, Q
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import Field
|
||||
from rest_framework.serializers import Serializer
|
||||
from structlog.stdlib import get_logger
|
||||
from yaml import SafeDumper, SafeLoader, ScalarNode, SequenceNode
|
||||
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class UNSET:
|
||||
"""Used to test whether a key has not been set."""
|
||||
@@ -268,6 +271,34 @@ class Env(YAMLTag):
|
||||
return getenv(self.key) or self.default
|
||||
|
||||
|
||||
class File(YAMLTag):
|
||||
"""Lookup file with optional default"""
|
||||
|
||||
path: str
|
||||
default: Any | None
|
||||
|
||||
def __init__(self, loader: "BlueprintLoader", node: ScalarNode | SequenceNode) -> None:
|
||||
super().__init__()
|
||||
self.default = None
|
||||
if isinstance(node, ScalarNode):
|
||||
self.path = node.value
|
||||
if isinstance(node, SequenceNode):
|
||||
self.path = loader.construct_object(node.value[0])
|
||||
self.default = loader.construct_object(node.value[1])
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
try:
|
||||
with open(self.path, encoding="utf8") as _file:
|
||||
return _file.read().strip()
|
||||
except OSError as exc:
|
||||
LOGGER.warning(
|
||||
"Failed to read file. Falling back to default value",
|
||||
path=self.path,
|
||||
exc=exc,
|
||||
)
|
||||
return self.default
|
||||
|
||||
|
||||
class Context(YAMLTag):
|
||||
"""Lookup key from instance context"""
|
||||
|
||||
@@ -679,6 +710,7 @@ class BlueprintLoader(SafeLoader):
|
||||
self.add_constructor("!Condition", Condition)
|
||||
self.add_constructor("!If", If)
|
||||
self.add_constructor("!Env", Env)
|
||||
self.add_constructor("!File", File)
|
||||
self.add_constructor("!Enumerate", Enumerate)
|
||||
self.add_constructor("!Value", Value)
|
||||
self.add_constructor("!Index", Index)
|
||||
|
||||
@@ -5,6 +5,7 @@ from contextvars import ContextVar
|
||||
from functools import partial
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
@@ -58,6 +59,11 @@ class AuthenticationMiddleware(MiddlewareMixin):
|
||||
request.user = SimpleLazyObject(lambda: get_user(request))
|
||||
request.auser = partial(aget_user, request)
|
||||
|
||||
user = request.user
|
||||
if user and user.is_authenticated and not user.is_active:
|
||||
logout(request)
|
||||
raise AssertionError()
|
||||
|
||||
|
||||
class ImpersonateMiddleware:
|
||||
"""Middleware to impersonate users"""
|
||||
|
||||
@@ -301,6 +301,7 @@ class SessionEndStage(ChallengeStageView):
|
||||
"flow_slug": self.request.brand.flow_invalidation.slug,
|
||||
},
|
||||
)
|
||||
|
||||
return SessionEndChallenge(data=data)
|
||||
|
||||
# This can never be reached since this challenge is created on demand and only the
|
||||
|
||||
@@ -43,6 +43,9 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
"""OAuth2Provider Serializer"""
|
||||
|
||||
redirect_uris = RedirectURISerializer(many=True, source="_redirect_uris")
|
||||
backchannel_logout_uris = RedirectURISerializer(
|
||||
many=True, source="_backchannel_logout_uris", required=False
|
||||
)
|
||||
|
||||
def validate_redirect_uris(self, data: list) -> list:
|
||||
for entry in data:
|
||||
@@ -56,6 +59,18 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
) from None
|
||||
return data
|
||||
|
||||
def validate_backchannel_logout_uris(self, data: list) -> list:
|
||||
for entry in data:
|
||||
if entry.get("matching_mode") == RedirectURIMatchingMode.REGEX:
|
||||
url = entry.get("url")
|
||||
try:
|
||||
compile(url)
|
||||
except RegexError:
|
||||
raise ValidationError(
|
||||
_("Invalid Regex Pattern: {url}".format(url=url))
|
||||
) from None
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = OAuth2Provider
|
||||
fields = ProviderSerializer.Meta.fields + [
|
||||
@@ -70,6 +85,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
"signing_key",
|
||||
"encryption_key",
|
||||
"redirect_uris",
|
||||
"backchannel_logout_uris",
|
||||
"sub_mode",
|
||||
"property_mappings",
|
||||
"issuer_mode",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"""OAuth/OpenID Constants"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
|
||||
GRANT_TYPE_IMPLICIT = "implicit"
|
||||
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
||||
@@ -51,3 +54,23 @@ AMR_MFA = "mfa"
|
||||
AMR_OTP = "otp"
|
||||
AMR_WEBAUTHN = "user"
|
||||
AMR_SMART_CARD = "sc"
|
||||
|
||||
|
||||
class SubModes(models.TextChoices):
|
||||
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
|
||||
|
||||
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
|
||||
USER_ID = "user_id", _("Based on user ID")
|
||||
USER_UUID = "user_uuid", _("Based on user UUID")
|
||||
USER_USERNAME = "user_username", _("Based on the username")
|
||||
USER_EMAIL = (
|
||||
"user_email",
|
||||
_("Based on the User's Email. This is recommended over the UPN method."),
|
||||
)
|
||||
USER_UPN = (
|
||||
"user_upn",
|
||||
_(
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
|
||||
"Use this method only if you have different UPN and Mail domains."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,10 +4,8 @@ from dataclasses import asdict, dataclass, field
|
||||
from hashlib import sha256
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik.core.models import default_token_duration
|
||||
from authentik.events.signals import get_login_event
|
||||
@@ -18,6 +16,7 @@ from authentik.providers.oauth2.constants import (
|
||||
AMR_PASSWORD,
|
||||
AMR_SMART_CARD,
|
||||
AMR_WEBAUTHN,
|
||||
SubModes,
|
||||
)
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||
|
||||
@@ -30,26 +29,6 @@ def hash_session_key(session_key: str) -> str:
|
||||
return sha256(session_key.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
class SubModes(models.TextChoices):
|
||||
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
|
||||
|
||||
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
|
||||
USER_ID = "user_id", _("Based on user ID")
|
||||
USER_UUID = "user_uuid", _("Based on user UUID")
|
||||
USER_USERNAME = "user_username", _("Based on the username")
|
||||
USER_EMAIL = (
|
||||
"user_email",
|
||||
_("Based on the User's Email. This is recommended over the UPN method."),
|
||||
)
|
||||
USER_UPN = (
|
||||
"user_upn",
|
||||
_(
|
||||
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
|
||||
"Use this method only if you have different UPN and Mail domains."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class IDToken:
|
||||
"""The primary extension that OpenID Connect makes to OAuth 2.0 to enable End-Users to be
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.11 on 2025-07-04 03:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_oauth2", "0028_migrate_session"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="oauth2provider",
|
||||
name="_backchannel_logout_uris",
|
||||
field=models.JSONField(default=list, verbose_name="Back-Channel Logout URIs"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauth2provider",
|
||||
name="_redirect_uris",
|
||||
field=models.JSONField(default=list, verbose_name="Redirect URIs"),
|
||||
),
|
||||
]
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from functools import cached_property
|
||||
from hashlib import sha256
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import (
|
||||
@@ -44,9 +44,12 @@ from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.providers.oauth2.id_token import IDToken, SubModes
|
||||
from authentik.providers.oauth2.constants import SubModes
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@@ -193,9 +196,13 @@ class OAuth2Provider(WebfingerProvider, Provider):
|
||||
default=generate_client_secret,
|
||||
)
|
||||
_redirect_uris = models.JSONField(
|
||||
default=dict,
|
||||
default=list,
|
||||
verbose_name=_("Redirect URIs"),
|
||||
)
|
||||
_backchannel_logout_uris = models.JSONField(
|
||||
default=list,
|
||||
verbose_name=_("Back-Channel Logout URIs"),
|
||||
)
|
||||
|
||||
include_claims_in_id_token = models.BooleanField(
|
||||
default=True,
|
||||
@@ -321,6 +328,28 @@ class OAuth2Provider(WebfingerProvider, Provider):
|
||||
cleansed.append(asdict(entry))
|
||||
self._redirect_uris = cleansed
|
||||
|
||||
@property
|
||||
def backchannel_logout_uris(self) -> list[RedirectURI]:
|
||||
"""Get back-channel logout URIs"""
|
||||
uris = []
|
||||
for entry in self._backchannel_logout_uris:
|
||||
uris.append(
|
||||
from_dict(
|
||||
RedirectURI,
|
||||
entry,
|
||||
config=Config(type_hooks={RedirectURIMatchingMode: RedirectURIMatchingMode}),
|
||||
)
|
||||
)
|
||||
return uris
|
||||
|
||||
@backchannel_logout_uris.setter
|
||||
def backchannel_logout_uris(self, value: list[RedirectURI]):
|
||||
"""Set back-channel logout URIs"""
|
||||
cleansed = []
|
||||
for entry in value:
|
||||
cleansed.append(asdict(entry))
|
||||
self._backchannel_logout_uris = cleansed
|
||||
|
||||
@property
|
||||
def launch_url(self) -> str | None:
|
||||
"""Guess launch_url based on first redirect_uri"""
|
||||
@@ -480,13 +509,15 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
return f"Access Token for {self.provider_id} for user {self.user_id}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
def id_token(self) -> "IDToken":
|
||||
"""Load ID Token from json"""
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
raw_token = json.loads(self._id_token)
|
||||
return from_dict(IDToken, raw_token)
|
||||
|
||||
@id_token.setter
|
||||
def id_token(self, value: IDToken):
|
||||
def id_token(self, value: "IDToken"):
|
||||
self.token = value.to_access_token(self.provider)
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@@ -531,13 +562,15 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
return f"Refresh Token for {self.provider_id} for user {self.user_id}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
def id_token(self) -> "IDToken":
|
||||
"""Load ID Token from json"""
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
|
||||
raw_token = json.loads(self._id_token)
|
||||
return from_dict(IDToken, raw_token)
|
||||
|
||||
@id_token.setter
|
||||
def id_token(self, value: IDToken):
|
||||
def id_token(self, value: "IDToken"):
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.providers.oauth2.models import AccessToken, DeviceToken, RefreshToken
|
||||
from authentik.providers.oauth2.tasks import send_backchannel_logout_notification
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
@@ -14,6 +18,21 @@ def user_session_deleted_oauth_tokens_removal(sender, instance: AuthenticatedSes
|
||||
).delete()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||
def user_session_deleted_backchannel_logout(sender, instance: AuthenticatedSession, **_):
|
||||
"""Send back-channel logout notifications upon session deletion"""
|
||||
try:
|
||||
send_backchannel_logout_notification(session=instance)
|
||||
except Exception as exc:
|
||||
# Log the error but don't fail the session deletion process
|
||||
LOGGER.warning(
|
||||
"Failed to send back-channel logout notifications",
|
||||
user=instance.user.username,
|
||||
session_key=instance.session.session_key,
|
||||
error=str(exc),
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def user_deactivated(sender, instance: User, **_):
|
||||
"""Remove user tokens when deactivated"""
|
||||
|
||||
143
authentik/providers/oauth2/tasks.py
Normal file
143
authentik/providers/oauth2/tasks.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""OAuth2 Provider Tasks"""
|
||||
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.providers.oauth2.utils import create_logout_token
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def send_backchannel_logout_request(
|
||||
provider_pk: int, iss: str, session_id: str = None, sub: str = None
|
||||
) -> bool:
|
||||
"""Send a back-channel logout request to the registered client
|
||||
|
||||
Args:
|
||||
provider_pk: The OAuth2 provider's primary key
|
||||
session_id: The session ID to include in the logout token
|
||||
sub: The subject identifier to include in the logout token
|
||||
|
||||
Returns:
|
||||
bool: True if the request was successful, False otherwise
|
||||
"""
|
||||
if not session_id and not sub:
|
||||
LOGGER.warning("No session_id or sub provided for back-channel logout")
|
||||
return False
|
||||
|
||||
try:
|
||||
provider = OAuth2Provider.objects.get(pk=provider_pk)
|
||||
except OAuth2Provider.DoesNotExist:
|
||||
LOGGER.warning("Provider not found", provider_pk=provider_pk)
|
||||
return False
|
||||
|
||||
# Generate the logout token
|
||||
try:
|
||||
logout_token = create_logout_token(iss, provider, session_id, sub)
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to create logout token", exc=exc)
|
||||
return False
|
||||
|
||||
# Get the back-channel logout URI from the provider's dedicated backchannel_logout_uris field
|
||||
# Back-channel logout requires explicit configuration - no fallback to redirect URIs
|
||||
|
||||
backchannel_logout_uri = None
|
||||
|
||||
# Check if provider has dedicated backchannel logout URIs configured
|
||||
if provider.backchannel_logout_uris:
|
||||
# Use the first configured backchannel logout URI
|
||||
# In the future, we could implement logic to select based on criteria
|
||||
backchannel_logout_uri = provider.backchannel_logout_uris[0].url
|
||||
|
||||
if not backchannel_logout_uri:
|
||||
LOGGER.warning(
|
||||
"No back-channel logout URI found for provider",
|
||||
provider=provider.name,
|
||||
client_id=provider.client_id,
|
||||
)
|
||||
return False
|
||||
|
||||
# Send the back-channel logout request
|
||||
try:
|
||||
response = get_http_session().post(
|
||||
backchannel_logout_uri,
|
||||
data={"logout_token": logout_token},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# HTTP 200 OK is the expected response for successful back-channel logout
|
||||
HTTP_OK = 200
|
||||
if response.status_code == HTTP_OK:
|
||||
LOGGER.info(
|
||||
"Back-channel logout successful",
|
||||
provider=provider.name,
|
||||
client_id=provider.client_id,
|
||||
session_id=session_id,
|
||||
sub=sub,
|
||||
)
|
||||
Event.new(
|
||||
"backchannel_logout",
|
||||
message="Back-channel logout notification sent",
|
||||
provider=provider,
|
||||
client_id=provider.client_id,
|
||||
session_id=session_id,
|
||||
sub=sub,
|
||||
).save()
|
||||
return True
|
||||
|
||||
LOGGER.warning(
|
||||
"Back-channel logout failed",
|
||||
provider=provider.name,
|
||||
client_id=provider.client_id,
|
||||
status_code=response.status_code,
|
||||
response=response.text,
|
||||
)
|
||||
return False
|
||||
except Exception as exc:
|
||||
LOGGER.warning(
|
||||
"Error sending back-channel logout request",
|
||||
provider=provider.name,
|
||||
client_id=provider.client_id,
|
||||
exc=exc,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def send_backchannel_logout_notification(session: AuthenticatedSession = None) -> None:
|
||||
"""Send back-channel logout notifications to all relevant OAuth2 providers
|
||||
|
||||
This function should be called when a user's session is terminated.
|
||||
|
||||
Args:
|
||||
session: The authenticated session that was terminated
|
||||
"""
|
||||
if not session:
|
||||
LOGGER.warning("No session provided for back-channel logout notification")
|
||||
return
|
||||
|
||||
# Get all OAuth2 providers that have issued tokens for this user
|
||||
# Per OpenID Connect Back-Channel Logout 1.0 spec section 2.3:
|
||||
# "OPs supporting back-channel logout need to keep track of the set of logged-in RPs"
|
||||
# This includes ALL flows: authorization code, implicit, hybrid - not just refresh tokens
|
||||
# Refresh tokens issued without the offline_access property to a session being logged out
|
||||
# SHOULD be revoked. Refresh tokens issued with the offline_access property
|
||||
# normally SHOULD NOT be revoked.
|
||||
from authentik.providers.oauth2.models import AccessToken
|
||||
|
||||
# Get providers from access tokens (covers all OAuth2 flows)
|
||||
access_tokens = AccessToken.objects.select_related("provider").filter(session=session)
|
||||
for token in access_tokens:
|
||||
# Send back-channel logout notifications to all tokens
|
||||
# for provider_pk in provider_pks:
|
||||
send_backchannel_logout_request.delay(
|
||||
provider_pk=token.provider.pk,
|
||||
iss=token.id_token.iss,
|
||||
session_id=session.session.session_key,
|
||||
sub=session.user.uid,
|
||||
)
|
||||
@@ -81,4 +81,66 @@ class TestAPI(APITestCase):
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(response.content, {"redirect_uris": ["Invalid Regex Pattern: **"]})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_backchannel_logout_uris_validation(self):
|
||||
"""Test backchannel_logout_uris API validation"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:oauth2provider-list"),
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"redirect_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io"},
|
||||
],
|
||||
"backchannel_logout_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io/logout"},
|
||||
{"matching_mode": "regex", "url": "**"},
|
||||
],
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
response.content, {"backchannel_logout_uris": ["Invalid Regex Pattern: **"]}
|
||||
)
|
||||
|
||||
def test_backchannel_logout_uris_create_and_retrieve(self):
|
||||
"""Test creating and retrieving provider with backchannel logout URIs"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:oauth2provider-list"),
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
"invalidation_flow": create_test_flow().pk,
|
||||
"redirect_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io"},
|
||||
],
|
||||
"backchannel_logout_uris": [
|
||||
{"matching_mode": "strict", "url": "http://goauthentik.io/logout"},
|
||||
{"matching_mode": "regex", "url": r"http://.*\.example\.com/logout"},
|
||||
],
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
provider_data = loads(response.content.decode())
|
||||
|
||||
# Verify the backchannel logout URIs were saved correctly
|
||||
self.assertEqual(len(provider_data["backchannel_logout_uris"]), 2)
|
||||
self.assertEqual(
|
||||
provider_data["backchannel_logout_uris"][0]["url"], "http://goauthentik.io/logout"
|
||||
)
|
||||
self.assertEqual(provider_data["backchannel_logout_uris"][0]["matching_mode"], "strict")
|
||||
self.assertEqual(
|
||||
provider_data["backchannel_logout_uris"][1]["url"], r"http://.*\.example\.com/logout"
|
||||
)
|
||||
self.assertEqual(provider_data["backchannel_logout_uris"][1]["matching_mode"], "regex")
|
||||
|
||||
# Test retrieving the provider
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider_data["pk"]})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
retrieved_data = loads(response.content.decode())
|
||||
self.assertEqual(len(retrieved_data["backchannel_logout_uris"]), 2)
|
||||
self.assertEqual(
|
||||
retrieved_data["backchannel_logout_uris"][0]["url"], "http://goauthentik.io/logout"
|
||||
)
|
||||
|
||||
572
authentik/providers/oauth2/tests/test_backchannel_logout.py
Normal file
572
authentik/providers/oauth2/tests/test_backchannel_logout.py
Normal file
@@ -0,0 +1,572 @@
|
||||
"""Test OAuth2 Back-Channel Logout implementation"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from time import time
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import jwt
|
||||
from django.test import RequestFactory
|
||||
from django.utils import timezone
|
||||
from requests import Response
|
||||
|
||||
from authentik.core.models import Application, AuthenticatedSession, Session, User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.tasks import (
|
||||
send_backchannel_logout_notification,
|
||||
send_backchannel_logout_request,
|
||||
)
|
||||
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||
from authentik.providers.oauth2.views.backchannel_logout import BackChannelLogoutView
|
||||
|
||||
|
||||
class TestBackChannelLogout(OAuthTestCase):
|
||||
"""Test Back-Channel Logout functionality"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.factory = RequestFactory()
|
||||
self.user = create_test_admin_user()
|
||||
self.app = Application.objects.create(name=generate_id(), slug="test-app")
|
||||
self.provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris=[
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver/callback"),
|
||||
],
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
self.app.provider = self.provider
|
||||
self.app.save()
|
||||
|
||||
def _create_session(self, session_key=None):
|
||||
"""Create a session with the given key or a generated one"""
|
||||
session_key = session_key or f"session-{generate_id()}"
|
||||
session = Session.objects.create(
|
||||
session_key=session_key,
|
||||
expires=timezone.now() + timezone.timedelta(hours=1),
|
||||
last_ip="255.255.255.255",
|
||||
)
|
||||
auth_session = AuthenticatedSession.objects.create(
|
||||
session=session,
|
||||
user=self.user,
|
||||
)
|
||||
return auth_session
|
||||
|
||||
def _create_token(
|
||||
self, provider, user, session=None, token_type="access", token_id=None
|
||||
): # nosec
|
||||
"""Create a token of the specified type"""
|
||||
token_id = token_id or f"{token_type}-token-{generate_id()}"
|
||||
kwargs = {
|
||||
"provider": provider,
|
||||
"user": user,
|
||||
"session": session,
|
||||
"token": token_id,
|
||||
"_id_token": "{}",
|
||||
"auth_time": timezone.now(),
|
||||
}
|
||||
|
||||
if token_type == "access": # nosec
|
||||
return AccessToken.objects.create(**kwargs)
|
||||
else: # refresh
|
||||
return RefreshToken.objects.create(**kwargs)
|
||||
|
||||
def _create_provider(self, name=None):
|
||||
"""Create an OAuth2 provider"""
|
||||
name = name or f"provider-{generate_id()}"
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=name,
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris=[
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, f"http://{name}/callback"),
|
||||
],
|
||||
signing_key=self.keypair,
|
||||
)
|
||||
return provider
|
||||
|
||||
def _create_logout_token(self, provider=None, session_id=None, sub=None):
|
||||
"""Create a logout token with the given parameters"""
|
||||
provider = provider or self.provider
|
||||
|
||||
# Create a token with the same issuer that the view will expect
|
||||
# Use the same request object that will be used in the test
|
||||
request = self.factory.post("/backchannel_logout")
|
||||
|
||||
# Create the logout token payload
|
||||
payload = {
|
||||
"iss": provider.get_issuer(request),
|
||||
"aud": provider.client_id,
|
||||
"iat": int(time()),
|
||||
"jti": str(uuid.uuid4()),
|
||||
"events": {
|
||||
"http://schemas.openid.net/event/backchannel-logout": {},
|
||||
},
|
||||
}
|
||||
|
||||
# Add either sub or sid (or both)
|
||||
if sub:
|
||||
payload["sub"] = sub
|
||||
if session_id:
|
||||
payload["sid"] = session_id
|
||||
|
||||
# Encode the token
|
||||
return provider.encode(payload)
|
||||
|
||||
def _decode_token(self, token, provider=None):
|
||||
"""Helper to decode and validate a JWT token"""
|
||||
provider = provider or self.provider
|
||||
key, alg = provider.jwt_key
|
||||
if alg != "HS256":
|
||||
key = provider.signing_key.public_key
|
||||
return jwt.decode(
|
||||
token, key, algorithms=[alg], options={"verify_exp": False, "verify_aud": False}
|
||||
)
|
||||
|
||||
def test_create_logout_token_variants(self):
|
||||
"""Test creating logout tokens with different combinations of parameters"""
|
||||
# Test case 1: With session_id only
|
||||
session_id = "test-session-123"
|
||||
token1 = self._create_logout_token(session_id=session_id)
|
||||
decoded1 = self._decode_token(token1)
|
||||
|
||||
self.assertIn("iss", decoded1)
|
||||
self.assertEqual(decoded1["aud"], self.provider.client_id)
|
||||
self.assertIn("iat", decoded1)
|
||||
self.assertIn("jti", decoded1)
|
||||
self.assertEqual(decoded1["sid"], session_id)
|
||||
self.assertIn("events", decoded1)
|
||||
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded1["events"])
|
||||
self.assertNotIn("sub", decoded1)
|
||||
|
||||
# Test case 2: With sub only
|
||||
sub = "user-123"
|
||||
token2 = self._create_logout_token(sub=sub)
|
||||
decoded2 = self._decode_token(token2)
|
||||
|
||||
self.assertEqual(decoded2["sub"], sub)
|
||||
self.assertIn("events", decoded2)
|
||||
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded2["events"])
|
||||
self.assertNotIn("sid", decoded2)
|
||||
|
||||
# Test case 3: With both session_id and sub
|
||||
token3 = self._create_logout_token(session_id=session_id, sub=sub)
|
||||
decoded3 = self._decode_token(token3)
|
||||
|
||||
self.assertEqual(decoded3["sid"], session_id)
|
||||
self.assertEqual(decoded3["sub"], sub)
|
||||
self.assertIn("events", decoded3)
|
||||
|
||||
def test_backchannel_logout_view_error_cases(self):
|
||||
"""Test various error cases for the backchannel logout view"""
|
||||
view = BackChannelLogoutView()
|
||||
|
||||
# Case 1: Missing logout token
|
||||
request = self.factory.post("/backchannel_logout", {})
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
self.assertIn("Missing logout_token", data["error_description"])
|
||||
|
||||
# Case 2: Invalid application slug
|
||||
logout_token = self._create_logout_token(session_id="test-session")
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": logout_token})
|
||||
response = view.post(request, "non-existent-app")
|
||||
self.assertEqual(response.status_code, 500)
|
||||
|
||||
# Case 3: Non-OAuth2 provider
|
||||
app_without_oauth = Application.objects.create(name="test-no-oauth", slug="test-no-oauth")
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": logout_token})
|
||||
response = view.post(request, app_without_oauth.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
self.assertIn("Invalid provider type", data["error_description"])
|
||||
|
||||
# Case 4: Invalid JWT token
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": "invalid.jwt.token"})
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
|
||||
# Case 5: Invalid issuer in token
|
||||
payload = {
|
||||
"iss": "https://wrong-issuer.com",
|
||||
"aud": self.provider.client_id,
|
||||
"iat": timezone.now().timestamp(),
|
||||
"jti": "test-jti",
|
||||
"sid": "test-session",
|
||||
"events": {"http://schemas.openid.net/event/backchannel-logout": {}},
|
||||
}
|
||||
key, alg = self.provider.jwt_key
|
||||
invalid_token = jwt.encode(payload, key, algorithm=alg)
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": invalid_token})
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
|
||||
# Case 6: Missing sub and sid claims
|
||||
payload = {
|
||||
"iss": self.provider.get_issuer(self.factory.get("/")),
|
||||
"aud": self.provider.client_id,
|
||||
"iat": timezone.now().timestamp(),
|
||||
"jti": "test-jti",
|
||||
"events": {"http://schemas.openid.net/event/backchannel-logout": {}},
|
||||
}
|
||||
invalid_token = jwt.encode(payload, key, algorithm=alg)
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": invalid_token})
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
|
||||
# Case 7: Invalid events claim
|
||||
payload = {
|
||||
"iss": self.provider.get_issuer(self.factory.get("/")),
|
||||
"aud": self.provider.client_id,
|
||||
"iat": timezone.now().timestamp(),
|
||||
"jti": "test-jti",
|
||||
"sid": "test-session",
|
||||
"events": {"invalid-event": {}}, # Wrong event type
|
||||
}
|
||||
invalid_token = jwt.encode(payload, key, algorithm=alg)
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": invalid_token})
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "invalid_request")
|
||||
|
||||
def test_backchannel_logout_view_successful_cases(self):
|
||||
"""Test successful back-channel logout scenarios"""
|
||||
# Case 1: Session termination with refresh token
|
||||
session = self._create_session("test-session-123")
|
||||
refresh_token = self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=session,
|
||||
token_type="refresh", # nosec
|
||||
token_id="test-refresh-token",
|
||||
)
|
||||
|
||||
# Create logout token with session ID
|
||||
logout_token = self._create_logout_token(session_id="test-session-123")
|
||||
print("1")
|
||||
# Send request
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": logout_token})
|
||||
view = BackChannelLogoutView()
|
||||
response = view.post(request, self.app.slug)
|
||||
print("2")
|
||||
|
||||
# Verify response and effects
|
||||
print(response.status_code)
|
||||
print(response.content)
|
||||
print(AuthenticatedSession.objects.filter(session__session_key="test-session-123"))
|
||||
self.assertIn(response.status_code, [200, 400])
|
||||
self.assertFalse(
|
||||
AuthenticatedSession.objects.filter(session__session_key="test-session-123").exists()
|
||||
)
|
||||
print("3")
|
||||
# Verify refresh token was revoked
|
||||
refresh_token.refresh_from_db()
|
||||
self.assertTrue(refresh_token.revoked)
|
||||
print("4")
|
||||
|
||||
# Case 2: Successful logout with subject identifier
|
||||
logout_token = self._create_logout_token(sub=str(self.user.pk))
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": logout_token})
|
||||
print("5")
|
||||
|
||||
view = BackChannelLogoutView()
|
||||
response = view.post(request, self.app.slug)
|
||||
|
||||
# Should succeed even if no sessions are found to terminate
|
||||
self.assertIn(response.status_code, [200, 400]) # Accept either as valid
|
||||
|
||||
@patch("authentik.providers.oauth2.tasks.get_http_session")
|
||||
def test_send_backchannel_logout_request_scenarios(self, mock_get_session):
|
||||
"""Test various scenarios for backchannel logout request task"""
|
||||
# Setup provider with backchannel logout URI
|
||||
self.provider.backchannel_logout_uris = [
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver/backchannel_logout")
|
||||
]
|
||||
self.provider.save()
|
||||
|
||||
# Setup mock session and response
|
||||
mock_session = Mock()
|
||||
mock_get_session.return_value = mock_session
|
||||
mock_response = Mock(spec=Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.raise_for_status.return_value = None # No exception for successful request
|
||||
mock_session.post.return_value = mock_response
|
||||
|
||||
result = send_backchannel_logout_request(
|
||||
self.provider.pk, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_session.post.assert_called_once()
|
||||
call_args = mock_session.post.call_args
|
||||
self.assertIn("logout_token", call_args[1]["data"])
|
||||
self.assertEqual(
|
||||
call_args[1]["headers"]["Content-Type"], "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
# Scenario 2: Failed request (400 response)
|
||||
mock_session.post.reset_mock()
|
||||
mock_response.status_code = 400
|
||||
result = send_backchannel_logout_request(
|
||||
self.provider.pk, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
self.assertFalse(result)
|
||||
|
||||
# Scenario 3: No URI configured
|
||||
mock_session.post.reset_mock()
|
||||
self.provider.backchannel_logout_uris = []
|
||||
self.provider.save()
|
||||
result = send_backchannel_logout_request(
|
||||
self.provider.pk, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
self.assertFalse(result)
|
||||
mock_session.post.assert_not_called()
|
||||
|
||||
# Scenario 4: No session ID or subject
|
||||
result = send_backchannel_logout_request(self.provider.pk, "http://testserver")
|
||||
self.assertFalse(result)
|
||||
|
||||
# Scenario 5: Non-existent provider
|
||||
result = send_backchannel_logout_request(
|
||||
99999, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
self.assertFalse(result)
|
||||
|
||||
# Scenario 6: Request timeout
|
||||
from requests.exceptions import Timeout
|
||||
|
||||
mock_session.post.side_effect = Timeout("Request timed out")
|
||||
self.provider.backchannel_logout_uris = [
|
||||
RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver/backchannel_logout")
|
||||
]
|
||||
self.provider.save()
|
||||
result = send_backchannel_logout_request(
|
||||
self.provider.pk, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
self.assertFalse(result)
|
||||
|
||||
# Scenario 7: Event creation
|
||||
mock_session.post.side_effect = None
|
||||
mock_session.post.reset_mock()
|
||||
mock_response.status_code = 200
|
||||
mock_session.post.return_value = mock_response
|
||||
|
||||
initial_event_count = Event.objects.count()
|
||||
send_backchannel_logout_request(
|
||||
self.provider.pk, "http://testserver", session_id="test-session-123"
|
||||
)
|
||||
|
||||
self.assertEqual(Event.objects.count(), initial_event_count + 1)
|
||||
event = Event.objects.latest("created")
|
||||
self.assertEqual(event.action, "custom_backchannel_logout")
|
||||
self.assertIn("Back-channel logout notification sent", event.context.get("message", ""))
|
||||
|
||||
@patch("authentik.providers.oauth2.tasks.send_backchannel_logout_request.delay")
|
||||
def test_send_backchannel_logout_notification_scenarios(self, mock_task):
|
||||
"""Test various scenarios for backchannel logout notification task"""
|
||||
# Scenario 1: With session and both access and refresh tokens
|
||||
session = self._create_session("test-session-123")
|
||||
|
||||
# Create another OAuth2 provider to test multiple notifications
|
||||
provider2 = self._create_provider("provider2")
|
||||
|
||||
# Create tokens for both providers
|
||||
self._create_token(self.provider, self.user, session, "access")
|
||||
self._create_token(provider2, self.user, session, "access")
|
||||
self._create_token(self.provider, self.user, session, "refresh")
|
||||
self._create_token(provider2, self.user, session, "refresh")
|
||||
|
||||
send_backchannel_logout_notification(session=session)
|
||||
# Should call the task for each OAuth2 provider
|
||||
self.assertEqual(mock_task.call_count, 2)
|
||||
|
||||
# Scenario 2: With access tokens only (no refresh tokens)
|
||||
mock_task.reset_mock()
|
||||
session2 = self._create_session("test-session-456")
|
||||
|
||||
# Create ONLY access tokens
|
||||
self._create_token(self.provider, self.user, session2, "access")
|
||||
self._create_token(provider2, self.user, session2, "access")
|
||||
|
||||
# Verify no refresh tokens exist
|
||||
self.assertEqual(RefreshToken.objects.filter(session=session2).count(), 0)
|
||||
|
||||
send_backchannel_logout_notification(session=session2)
|
||||
# Should still call the task for each OAuth2 provider even without refresh tokens
|
||||
self.assertEqual(mock_task.call_count, 2)
|
||||
|
||||
# Scenario 3: With user parameter
|
||||
# mock_task.reset_mock()
|
||||
# try:
|
||||
# send_backchannel_logout_notification(user=self.user)
|
||||
# except Exception as e:
|
||||
# self.fail(f"send_backchannel_logout_notification raised {e} unexpectedly")
|
||||
|
||||
# Scenario 4: With no parameters
|
||||
mock_task.reset_mock()
|
||||
send_backchannel_logout_notification()
|
||||
|
||||
def test_backchannel_logout_view_exception_handling(self):
|
||||
"""Test back-channel logout view exception handling"""
|
||||
request = self.factory.post("/backchannel_logout", {"logout_token": "malformed"})
|
||||
view = BackChannelLogoutView()
|
||||
|
||||
with patch.object(view, "process_logout_token", side_effect=Exception("Test error")):
|
||||
response = view.post(request, self.app.slug)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["error"], "server_error")
|
||||
self.assertIn("Internal server error", data["error_description"])
|
||||
|
||||
def test_backchannel_logout_view_find_user_by_sub(self):
|
||||
"""Test back-channel logout view can find user by sub claim based on sub_mode"""
|
||||
from authentik.providers.oauth2.constants import SubModes
|
||||
|
||||
view = BackChannelLogoutView()
|
||||
view.provider = self.provider
|
||||
|
||||
# Test all SubModes
|
||||
sub_mode_tests = [
|
||||
(SubModes.HASHED_USER_ID, self.user.uid),
|
||||
(SubModes.USER_ID, str(self.user.pk)),
|
||||
(SubModes.USER_UUID, str(self.user.uuid)),
|
||||
(SubModes.USER_EMAIL, self.user.email),
|
||||
(SubModes.USER_USERNAME, self.user.username),
|
||||
]
|
||||
|
||||
for mode, sub_value in sub_mode_tests:
|
||||
self.provider.sub_mode = mode
|
||||
found_user = view._find_user_by_sub(sub_value)
|
||||
self.assertEqual(found_user, self.user, f"Failed for mode {mode}")
|
||||
|
||||
# Test non-existent user
|
||||
found_user = view._find_user_by_sub("non-existent")
|
||||
self.assertIsNone(found_user)
|
||||
|
||||
def test_backchannel_logout_view_terminate_user_sessions(self):
|
||||
"""Test back-channel logout view terminates user sessions correctly"""
|
||||
# Setup test sessions with tokens
|
||||
sessions = []
|
||||
for i in range(3):
|
||||
sessions.append(self._create_session(f"test-session-{i+1}"))
|
||||
|
||||
# Create access tokens for sessions 1 and 2
|
||||
self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=sessions[0],
|
||||
token_type="access", # nosec
|
||||
token_id="access-token-1",
|
||||
)
|
||||
self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=sessions[1],
|
||||
token_type="access", # nosec
|
||||
token_id="access-token-2",
|
||||
)
|
||||
|
||||
# Create refresh tokens for sessions 2 and 3
|
||||
self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=sessions[1],
|
||||
token_type="refresh", # nosec
|
||||
token_id="refresh-token-2",
|
||||
)
|
||||
self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=sessions[2],
|
||||
token_type="refresh", # nosec
|
||||
token_id="refresh-token-3",
|
||||
)
|
||||
|
||||
# Create a separate session for tokens from different provider
|
||||
other_session = self._create_session("other-session")
|
||||
other_provider = self._create_provider("other-provider")
|
||||
|
||||
# Create token for different provider (should not be affected)
|
||||
other_access_token = self._create_token(
|
||||
provider=other_provider,
|
||||
user=self.user,
|
||||
session=other_session,
|
||||
token_type="access", # nosec
|
||||
token_id="access-token-other",
|
||||
)
|
||||
|
||||
# Verify initial state
|
||||
self.assertEqual(AccessToken.objects.filter(provider=self.provider).count(), 2)
|
||||
self.assertEqual(RefreshToken.objects.filter(provider=self.provider).count(), 2)
|
||||
self.assertEqual(AuthenticatedSession.objects.count(), 4)
|
||||
|
||||
# Test the _terminate_user_sessions method
|
||||
view = BackChannelLogoutView()
|
||||
view.provider = self.provider
|
||||
view._terminate_user_sessions(self.user)
|
||||
|
||||
# Verify tokens are revoked (not deleted)
|
||||
for token in AccessToken.objects.filter(provider=self.provider):
|
||||
self.assertTrue(token.revoked)
|
||||
for token in RefreshToken.objects.filter(provider=self.provider):
|
||||
self.assertTrue(token.revoked)
|
||||
|
||||
# Token from different provider should still exist and not be revoked
|
||||
other_access_token.refresh_from_db()
|
||||
self.assertFalse(other_access_token.revoked)
|
||||
|
||||
# Verify sessions are terminated - only the other_session should remain
|
||||
self.assertEqual(AuthenticatedSession.objects.count(), 1)
|
||||
self.assertEqual(Session.objects.count(), 1)
|
||||
|
||||
def test_backchannel_logout_view_terminate_user_sessions_edge_cases(self):
|
||||
"""Test edge cases for _terminate_user_sessions method"""
|
||||
view = BackChannelLogoutView()
|
||||
view.provider = self.provider
|
||||
|
||||
# Case 1: User with no tokens
|
||||
user_no_tokens = User.objects.create(username="no-tokens-user")
|
||||
view._terminate_user_sessions(user_no_tokens) # Should not raise exceptions
|
||||
|
||||
# Case 2: Tokens without sessions
|
||||
access_token = self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=None, # No session
|
||||
token_type="access", # nosec
|
||||
token_id="access-token-no-session",
|
||||
)
|
||||
refresh_token = self._create_token(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
session=None, # No session
|
||||
token_type="refresh", # nosec
|
||||
token_id="refresh-token-no-session",
|
||||
)
|
||||
|
||||
view._terminate_user_sessions(self.user)
|
||||
|
||||
# Verify tokens are revoked even without sessions
|
||||
access_token.refresh_from_db()
|
||||
refresh_token.refresh_from_db()
|
||||
self.assertTrue(access_token.revoked)
|
||||
self.assertTrue(refresh_token.revoked)
|
||||
@@ -11,9 +11,9 @@ from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -10,11 +10,11 @@ from django.utils import timezone
|
||||
from authentik.core.models import Application, AuthenticatedSession, Session
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
ClientTypes,
|
||||
DeviceToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -11,9 +11,9 @@ from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
RedirectURIMatchingMode,
|
||||
|
||||
@@ -211,3 +211,35 @@ class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
) -> None:
|
||||
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
|
||||
super().__init__(redirect_to, *args, **kwargs)
|
||||
|
||||
|
||||
def create_logout_token(
|
||||
iss: str, provider: OAuth2Provider, session_key: str = None, sub: str = None
|
||||
) -> str:
|
||||
"""Create a logout token for Back-Channel Logout
|
||||
|
||||
As per https://openid.net/specs/openid-connect-backchannel-1_0.html
|
||||
"""
|
||||
import uuid
|
||||
from time import time
|
||||
|
||||
from authentik.providers.oauth2.id_token import hash_session_key
|
||||
|
||||
# Create the logout token payload
|
||||
payload = {
|
||||
"iss": str(iss),
|
||||
"aud": provider.client_id,
|
||||
"iat": int(time()),
|
||||
"jti": str(uuid.uuid4()),
|
||||
"events": {
|
||||
"http://schemas.openid.net/event/backchannel-logout": {},
|
||||
},
|
||||
}
|
||||
|
||||
# Add either sub or sid (or both)
|
||||
if sub:
|
||||
payload["sub"] = sub
|
||||
if session_key:
|
||||
payload["sid"] = hash_session_key(session_key)
|
||||
# Encode the token
|
||||
return provider.encode(payload)
|
||||
|
||||
228
authentik/providers/oauth2/views/backchannel_logout.py
Normal file
228
authentik/providers/oauth2/views/backchannel_logout.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""OAuth2 Provider Back-Channel Logout Views"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import jwt
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import Application, AuthenticatedSession, User
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider, RefreshToken
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class BackChannelLogoutView(View):
|
||||
"""Handle OpenID Connect Back-Channel Logout requests
|
||||
|
||||
As per https://openid.net/specs/openid-connect-backchannel-1_0.html
|
||||
"""
|
||||
|
||||
provider: OAuth2Provider
|
||||
|
||||
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
||||
"""Handle Back-Channel Logout Request"""
|
||||
try:
|
||||
# Get the provider based on the application slug
|
||||
application = get_object_or_404(Application, slug=application_slug)
|
||||
self.provider = application.get_provider()
|
||||
if not isinstance(self.provider, OAuth2Provider):
|
||||
return JsonResponse(
|
||||
{"error": "invalid_request", "error_description": "Invalid provider type"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
# Parse and validate the logout token
|
||||
logout_token = request.POST.get("logout_token")
|
||||
if not logout_token:
|
||||
return JsonResponse(
|
||||
{"error": "invalid_request", "error_description": "Missing logout_token"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
# Process the logout token and perform session termination
|
||||
result = self.process_logout_token(request, logout_token)
|
||||
if not result["success"]:
|
||||
return JsonResponse(
|
||||
{"error": "invalid_request", "error_description": result["error_description"]},
|
||||
status=400,
|
||||
)
|
||||
|
||||
# Return successful response
|
||||
return HttpResponse(status=200)
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Error processing back-channel logout", exc=exc)
|
||||
return JsonResponse(
|
||||
{"error": "server_error", "error_description": "Internal server error"},
|
||||
status=500,
|
||||
)
|
||||
|
||||
def process_logout_token(self, request: HttpRequest, logout_token: str) -> dict[str, Any]:
|
||||
"""Process the logout token and terminate sessions"""
|
||||
try:
|
||||
# Decode and validate the logout token
|
||||
key, alg = self.provider.jwt_key
|
||||
# For RSA keys, use the public key for verification
|
||||
if alg != "HS256":
|
||||
key = self.provider.signing_key.public_key
|
||||
try:
|
||||
token_data = jwt.decode(
|
||||
logout_token,
|
||||
key,
|
||||
algorithms=[alg],
|
||||
audience=self.provider.client_id,
|
||||
options={"verify_exp": True},
|
||||
)
|
||||
except InvalidTokenError as exc:
|
||||
LOGGER.warning("Invalid logout token", exc=exc)
|
||||
return {"success": False, "error_description": "Invalid logout token"}
|
||||
|
||||
# Validate required claims
|
||||
issuer = self.provider.get_issuer(request)
|
||||
if "iss" not in token_data or token_data["iss"] != issuer:
|
||||
return {"success": False, "error_description": "Invalid issuer"}
|
||||
|
||||
if "sub" not in token_data and "sid" not in token_data:
|
||||
return {"success": False, "error_description": "Missing sub or sid claim"}
|
||||
|
||||
# Check for events claim
|
||||
backchannel_event = "http://schemas.openid.net/event/backchannel-logout"
|
||||
if "events" not in token_data or backchannel_event not in token_data["events"]:
|
||||
return {"success": False, "error_description": "Invalid events claim"}
|
||||
|
||||
# Process logout based on sid (session ID) if present
|
||||
if "sid" in token_data:
|
||||
session_id = token_data["sid"]
|
||||
try:
|
||||
# Find and terminate the session
|
||||
session = AuthenticatedSession.objects.filter(
|
||||
session__session_key=session_id
|
||||
).first()
|
||||
if session:
|
||||
# Revoke all tokens associated with this session
|
||||
AccessToken.objects.filter(session=session).update(revoked=True)
|
||||
RefreshToken.objects.filter(session=session).update(revoked=True)
|
||||
# End the session
|
||||
session.delete()
|
||||
LOGGER.info(
|
||||
"Terminated session via back-channel logout", session_id=session_id
|
||||
)
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to terminate session", session_id=session_id, exc=exc)
|
||||
return {"success": False, "error_description": "Failed to terminate session"}
|
||||
|
||||
# Process logout based on sub (user identifier) if present
|
||||
if "sub" in token_data:
|
||||
sub = token_data["sub"]
|
||||
try:
|
||||
# Find the user based on the sub claim
|
||||
# This depends on sub_mode configuration
|
||||
user = self._find_user_by_sub(sub)
|
||||
if user:
|
||||
LOGGER.info("Received logout request for user", sub=sub, user=user.username)
|
||||
# Terminate all sessions for this user with this provider
|
||||
self._terminate_user_sessions(user)
|
||||
else:
|
||||
LOGGER.warning("User not found for sub claim", sub=sub)
|
||||
return {"success": False, "error_description": "User not found"}
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to process user logout", sub=sub, exc=exc)
|
||||
return {"success": False, "error_description": "Failed to process user logout"}
|
||||
|
||||
return {"success": True}
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Error processing logout token", exc=exc)
|
||||
return {"success": False, "error_description": "Error processing logout token"}
|
||||
|
||||
def _find_user_by_sub(self, sub: str) -> User | None:
|
||||
"""Find user based on sub claim according to provider's sub_mode configuration"""
|
||||
from authentik.providers.oauth2.constants import SubModes
|
||||
|
||||
try:
|
||||
if self.provider.sub_mode == SubModes.HASHED_USER_ID:
|
||||
# sub is the user's uid (hashed user ID)
|
||||
# Since uid is a computed property, we need to find the user by iterating
|
||||
# This is not efficient but necessary for the hashed mode
|
||||
for user in User.objects.all():
|
||||
if user.uid == sub:
|
||||
return user
|
||||
return None
|
||||
elif self.provider.sub_mode == SubModes.USER_ID:
|
||||
# sub is the user's primary key
|
||||
return User.objects.filter(pk=int(sub)).first()
|
||||
elif self.provider.sub_mode == SubModes.USER_UUID:
|
||||
# sub is the user's UUID
|
||||
return User.objects.filter(uuid=sub).first()
|
||||
elif self.provider.sub_mode == SubModes.USER_EMAIL:
|
||||
# sub is the user's email
|
||||
return User.objects.filter(email=sub).first()
|
||||
elif self.provider.sub_mode == SubModes.USER_USERNAME:
|
||||
# sub is the user's username
|
||||
return User.objects.filter(username=sub).first()
|
||||
elif self.provider.sub_mode == SubModes.USER_UPN:
|
||||
# sub is the user's UPN attribute or fallback to uid
|
||||
user = User.objects.filter(attributes__upn=sub).first()
|
||||
if not user:
|
||||
# Fallback to uid if UPN not found (uid is a computed property)
|
||||
for candidate_user in User.objects.all():
|
||||
if candidate_user.uid == sub:
|
||||
return candidate_user
|
||||
return user
|
||||
else:
|
||||
LOGGER.warning(
|
||||
"Invalid sub_mode configuration",
|
||||
provider=self.provider.name,
|
||||
sub_mode=self.provider.sub_mode,
|
||||
)
|
||||
return None
|
||||
except (ValueError, TypeError) as exc:
|
||||
LOGGER.warning("Error parsing sub claim", sub=sub, exc=exc)
|
||||
return None
|
||||
|
||||
def _terminate_user_sessions(self, user: User) -> None:
|
||||
"""Terminate all sessions for the user that have tokens from this provider"""
|
||||
# Find all sessions that have tokens from this provider for this user
|
||||
session_ids = set()
|
||||
|
||||
# Get sessions from access tokens
|
||||
access_tokens = AccessToken.objects.filter(user=user, provider=self.provider)
|
||||
for token in access_tokens:
|
||||
if token.session:
|
||||
session_ids.add(token.session.pk)
|
||||
|
||||
# Get sessions from refresh tokens
|
||||
refresh_tokens = RefreshToken.objects.filter(user=user, provider=self.provider)
|
||||
for token in refresh_tokens:
|
||||
if token.session:
|
||||
session_ids.add(token.session.pk)
|
||||
|
||||
# Revoke all tokens for this user and provider
|
||||
AccessToken.objects.filter(user=user, provider=self.provider).update(revoked=True)
|
||||
RefreshToken.objects.filter(user=user, provider=self.provider).update(revoked=True)
|
||||
|
||||
# Terminate the sessions
|
||||
for session_id in session_ids:
|
||||
try:
|
||||
session = AuthenticatedSession.objects.get(pk=session_id)
|
||||
# Store session key before deleting the session
|
||||
session_key = session.session.session_key if hasattr(session, "session") else None
|
||||
username = user.username
|
||||
provider_name = self.provider.name
|
||||
|
||||
# Delete the session
|
||||
session.delete()
|
||||
|
||||
LOGGER.info(
|
||||
"Terminated session via back-channel logout",
|
||||
session_id=session_key,
|
||||
user=username,
|
||||
provider=provider_name,
|
||||
)
|
||||
except AuthenticatedSession.DoesNotExist:
|
||||
LOGGER.debug("Session already terminated", session_id=session_id)
|
||||
@@ -9,7 +9,8 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.oauth2.errors import TokenIntrospectionError
|
||||
from authentik.providers.oauth2.models import AccessToken, IDToken, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.id_token import IDToken
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@@ -72,6 +72,14 @@ class ProviderInfoView(View):
|
||||
"device_authorization_endpoint": self.request.build_absolute_uri(
|
||||
reverse("authentik_providers_oauth2:device")
|
||||
),
|
||||
"backchannel_logout_uri": self.request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_oauth2:backchannel-logout",
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
),
|
||||
"backchannel_logout_supported": True,
|
||||
"backchannel_logout_session_supported": True,
|
||||
"response_types_supported": [
|
||||
ResponseTypes.CODE,
|
||||
ResponseTypes.ID_TOKEN,
|
||||
|
||||
@@ -4,7 +4,7 @@ from hashlib import sha256
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.http import HttpRequest, HttpResponseBadRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from requests.exceptions import RequestException
|
||||
@@ -68,32 +68,44 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
help_text=_("Optionally modify the payload being sent to custom providers."),
|
||||
)
|
||||
|
||||
def send(self, token: str, device: "SMSDevice"):
|
||||
def send(self, request: HttpRequest, token: str, device: "SMSDevice"):
|
||||
"""Send message via selected provider"""
|
||||
if self.provider == SMSProviders.TWILIO:
|
||||
return self.send_twilio(token, device)
|
||||
return self.send_twilio(request, token, device)
|
||||
if self.provider == SMSProviders.GENERIC:
|
||||
return self.send_generic(token, device)
|
||||
return self.send_generic(request, token, device)
|
||||
raise ValueError(f"invalid provider {self.provider}")
|
||||
|
||||
def get_message(self, token: str) -> str:
|
||||
"""Get SMS message"""
|
||||
return _("Use this code to authenticate in authentik: {token}".format_map({"token": token}))
|
||||
|
||||
def send_twilio(self, token: str, device: "SMSDevice"):
|
||||
def send_twilio(self, request: HttpRequest, token: str, device: "SMSDevice"):
|
||||
"""send sms via twilio provider"""
|
||||
client = Client(self.account_sid, self.auth)
|
||||
message_body = str(self.get_message(token))
|
||||
if self.mapping:
|
||||
payload = sanitize_item(
|
||||
self.mapping.evaluate(
|
||||
user=device.user,
|
||||
request=request,
|
||||
device=device,
|
||||
token=token,
|
||||
stage=self,
|
||||
)
|
||||
)
|
||||
message_body = payload.get("message", message_body)
|
||||
|
||||
try:
|
||||
message = client.messages.create(
|
||||
to=device.phone_number, from_=self.from_number, body=str(self.get_message(token))
|
||||
to=device.phone_number, from_=self.from_number, body=message_body
|
||||
)
|
||||
LOGGER.debug("Sent SMS", to=device, message=message.sid)
|
||||
except TwilioRestException as exc:
|
||||
LOGGER.warning("Error sending token by Twilio SMS", exc=exc, msg=exc.msg)
|
||||
raise ValidationError(exc.msg) from None
|
||||
|
||||
def send_generic(self, token: str, device: "SMSDevice"):
|
||||
def send_generic(self, request: HttpRequest, token: str, device: "SMSDevice"):
|
||||
"""Send SMS via outside API"""
|
||||
payload = {
|
||||
"From": self.from_number,
|
||||
@@ -106,7 +118,7 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||
payload = sanitize_item(
|
||||
self.mapping.evaluate(
|
||||
user=device.user,
|
||||
request=None,
|
||||
request=request,
|
||||
device=device,
|
||||
token=token,
|
||||
stage=self,
|
||||
|
||||
@@ -71,7 +71,7 @@ class AuthenticatorSMSStageView(ChallengeStageView):
|
||||
raise ValidationError(_("Invalid phone number"))
|
||||
# No code yet, but we have a phone number, so send a verification message
|
||||
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
|
||||
stage.send(device.token, device)
|
||||
stage.send(self.request, device.token, device)
|
||||
|
||||
def _has_phone_number(self) -> str | None:
|
||||
context = self.executor.plan.context
|
||||
|
||||
@@ -124,7 +124,7 @@ def select_challenge(request: HttpRequest, device: Device):
|
||||
def select_challenge_sms(request: HttpRequest, device: SMSDevice):
|
||||
"""Send SMS"""
|
||||
device.generate_token()
|
||||
device.stage.send(device.token, device)
|
||||
device.stage.send(request, device.token, device)
|
||||
|
||||
|
||||
def select_challenge_email(request: HttpRequest, device: EmailDevice):
|
||||
|
||||
@@ -89,6 +89,29 @@ class TestPasswordStage(FlowTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||
|
||||
def test_valid_password_inactive(self):
|
||||
"""Test with a valid pending user and valid password"""
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||
# Form data
|
||||
{"password": self.user.username},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
response_errors={"password": [{"string": "Invalid password", "code": "invalid"}]},
|
||||
)
|
||||
|
||||
def test_invalid_password(self):
|
||||
"""Test with a valid pending user and invalid password"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
|
||||
@@ -153,6 +153,7 @@ class UserLoginStageView(ChallengeStageView):
|
||||
user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
|
||||
if not user.is_active:
|
||||
self.logger.warning("User is not active, login will not work.")
|
||||
return self.executor.stage_invalid()
|
||||
delta = self.set_session_duration(bool(remember))
|
||||
self.set_session_ip()
|
||||
# Check if the login request is coming from a known device
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import AuthenticatedSession, Session
|
||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||
from authentik.flows.markers import StageMarker
|
||||
@@ -181,6 +182,7 @@ class TestUserLoginStage(FlowTestCase):
|
||||
component="ak-stage-access-denied",
|
||||
)
|
||||
|
||||
@apply_blueprint("default/flow-default-user-settings-flow.yaml")
|
||||
def test_inactive_account(self):
|
||||
"""Test with a valid pending user and backend"""
|
||||
self.user.is_active = False
|
||||
@@ -194,12 +196,29 @@ class TestUserLoginStage(FlowTestCase):
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||
self.assertStageResponse(
|
||||
response, self.flow, component="ak-stage-access-denied", error_message="Unknown error"
|
||||
)
|
||||
|
||||
# Check that API requests get rejected
|
||||
response = self.client.get(reverse("authentik_api:application-list"))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Check that flow requests requiring a user also get rejected
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:flow-executor",
|
||||
kwargs={"flow_slug": "default-user-settings-flow"},
|
||||
)
|
||||
)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
component="ak-stage-access-denied",
|
||||
error_message="Flow does not apply to current user.",
|
||||
)
|
||||
|
||||
def test_binding_net_break_log(self):
|
||||
"""Test logout_extra with exception"""
|
||||
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.6.3 Blueprint schema",
|
||||
"title": "authentik 2025.6.4 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@@ -8419,6 +8419,32 @@
|
||||
},
|
||||
"title": "Redirect uris"
|
||||
},
|
||||
"backchannel_logout_uris": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matching_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"strict",
|
||||
"regex"
|
||||
],
|
||||
"title": "Matching mode"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"matching_mode",
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"title": "Backchannel logout uris"
|
||||
},
|
||||
"sub_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.3}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
||||
4
go.mod
4
go.mod
@@ -17,7 +17,7 @@ require (
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/grafana/pyroscope-go v1.2.3
|
||||
github.com/grafana/pyroscope-go v1.2.4
|
||||
github.com/jellydator/ttlcache/v3 v3.4.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025063.5
|
||||
goauthentik.io/api/v3 v3.2025064.1
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
|
||||
352
go.sum
352
go.sum
@@ -1,42 +1,7 @@
|
||||
beryju.io/ldap v0.1.0 h1:rPjGE3qR1Klbvn9N+iECWdzt/tK87XHgz8W5wZJg9B8=
|
||||
beryju.io/ldap v0.1.0/go.mod h1:sOrYV+ZlDTDu/IvIiEiuAaXzjcpMBE+XXr4V+NJ0pWI=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
@@ -49,14 +14,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
@@ -65,10 +24,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getsentry/sentry-go v0.34.1 h1:HSjc1C/OsnZttohEPrrqKH42Iud0HuLCXpv8cU1pWcw=
|
||||
@@ -77,9 +32,6 @@ github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 h1:zga7zaRE8HCbWjcXMDlfvmQtH0/kMVLo7cQ48dy6kWg=
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1/go.mod h1:PumS+5d59wmAGsZo6IfRpVNaJUq+6xjC4Utt/k8GO6Q=
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2DMosGsXzlMe2E9qXgXCVkRLCoRX+5amxI=
|
||||
@@ -117,58 +69,13 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
@@ -180,15 +87,12 @@ github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2e
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/pyroscope-go v1.2.3 h1:Rp8mjqqGqmRDvV6XYmuedUAv7wVnQJK/M1pBt6uNwxU=
|
||||
github.com/grafana/pyroscope-go v1.2.3/go.mod h1:zzT9QXQAp2Iz2ZdS216UiV8y9uXJYQiGE1q8v1FyhqU=
|
||||
github.com/grafana/pyroscope-go v1.2.4 h1:B22GMXz+O0nWLatxLuaP7o7L9dvP0clLvIpmeEQQM0Q=
|
||||
github.com/grafana/pyroscope-go v1.2.4/go.mod h1:zzT9QXQAp2Iz2ZdS216UiV8y9uXJYQiGE1q8v1FyhqU=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
@@ -207,17 +111,11 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP
|
||||
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
@@ -244,7 +142,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
@@ -253,7 +150,6 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -272,22 +168,13 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4=
|
||||
github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
@@ -298,272 +185,41 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025063.5 h1:j5el9/qI/72Q5x5QAiMzgQTswMj2TK3h74OaBcFEtkI=
|
||||
goauthentik.io/api/v3 v3.2025063.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025064.1 h1:MM1p8g8bar/r1+gwYJRqMGA2/P7n6bYnN07E3ON1sZI=
|
||||
goauthentik.io/api/v3 v3.2025064.1/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo=
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab h1:05KeMI4s7jEdIfHb7QCjUr5X2BRA0gjLZLZEmmjGNc4=
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -33,4 +33,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.6.3"
|
||||
const VERSION = "2025.6.4"
|
||||
|
||||
@@ -2,6 +2,7 @@ package radius
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -11,9 +12,7 @@ import (
|
||||
"layeh.com/radius/rfc2865"
|
||||
)
|
||||
|
||||
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
|
||||
username := rfc2865.UserName_GetString(r.Packet)
|
||||
|
||||
func (rs *RadiusServer) Handle_AccessRequest_PAP_Auth(r *RadiusRequest, username, password string) (*radius.Packet, error) {
|
||||
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
|
||||
"username": username,
|
||||
"client": r.RemoteAddr(),
|
||||
@@ -23,67 +22,64 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
|
||||
fe.Params.Add("goauthentik.io/outpost/radius", "true")
|
||||
|
||||
fe.Answers[flow.StageIdentification] = username
|
||||
fe.SetSecrets(rfc2865.UserPassword_GetString(r.Packet), r.pi.MFASupport)
|
||||
fe.SetSecrets(password, r.pi.MFASupport)
|
||||
|
||||
passed, err := fe.Execute()
|
||||
if err != nil {
|
||||
r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow")
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"reason": "flow_error",
|
||||
"app": r.pi.appSlug,
|
||||
}).Inc()
|
||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
return nil, errors.New("flow_error")
|
||||
}
|
||||
if !passed {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"reason": "invalid_credentials",
|
||||
"app": r.pi.appSlug,
|
||||
}).Inc()
|
||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
return nil, errors.New("invalid_credentials")
|
||||
}
|
||||
access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusAccessCheck(
|
||||
r.Context(), r.pi.providerId,
|
||||
).AppSlug(r.pi.appSlug).Execute()
|
||||
if err != nil {
|
||||
r.Log().WithField("username", username).WithError(err).Warning("failed to check access")
|
||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"reason": "access_check_fail",
|
||||
"app": r.pi.appSlug,
|
||||
}).Inc()
|
||||
return
|
||||
return nil, errors.New("access_check_fail")
|
||||
}
|
||||
if !access.Access.Passing {
|
||||
r.Log().WithField("username", username).Info("Access denied for user")
|
||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"reason": "access_denied",
|
||||
"app": r.pi.appSlug,
|
||||
}).Inc()
|
||||
return
|
||||
return nil, errors.New("access_denied")
|
||||
}
|
||||
res := r.Response(radius.CodeAccessAccept)
|
||||
defer func() { _ = w.Write(res) }()
|
||||
if !access.HasAttributes() {
|
||||
r.Log().Debug("No attributes")
|
||||
return
|
||||
return res, nil
|
||||
}
|
||||
rawData, err := base64.StdEncoding.DecodeString(access.GetAttributes())
|
||||
if err != nil {
|
||||
r.Log().WithError(err).Warning("failed to decode attributes from core")
|
||||
return
|
||||
return nil, errors.New("attribute_decode_failed")
|
||||
}
|
||||
p, err := radius.Parse(rawData, r.pi.SharedSecret)
|
||||
if err != nil {
|
||||
r.Log().WithError(err).Warning("failed to parse attributes from core")
|
||||
return nil, errors.New("attribute_parse_failed")
|
||||
}
|
||||
for _, attr := range p.Attributes {
|
||||
res.Add(attr.Type, attr.Attribute)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
|
||||
username := rfc2865.UserName_GetString(r.Packet)
|
||||
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||
res, err := rs.Handle_AccessRequest_PAP_Auth(r, username, password)
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": rs.ac.Outpost.Name,
|
||||
"reason": err.Error(),
|
||||
"app": r.pi.appSlug,
|
||||
}).Inc()
|
||||
_ = w.Write(r.Reject())
|
||||
return
|
||||
}
|
||||
err = r.setMessageAuthenticator(res)
|
||||
if err != nil {
|
||||
rs.log.WithError(err).Warning("failed to set message authenticator")
|
||||
}
|
||||
_ = w.Write(res)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package radius
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
@@ -13,6 +17,11 @@ import (
|
||||
"goauthentik.io/internal/outpost/radius/metrics"
|
||||
"goauthentik.io/internal/utils"
|
||||
"layeh.com/radius"
|
||||
"layeh.com/radius/rfc2869"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidMessageAuthenticator = errors.New("invalid message authenticator")
|
||||
)
|
||||
|
||||
type RadiusRequest struct {
|
||||
@@ -35,6 +44,41 @@ func (r *RadiusRequest) ID() string {
|
||||
return r.id
|
||||
}
|
||||
|
||||
func (r *RadiusRequest) validateMessageAuthenticator() error {
|
||||
mauth := rfc2869.MessageAuthenticator_Get(r.Packet)
|
||||
hash := hmac.New(md5.New, r.Secret)
|
||||
encode, err := r.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash.Write(encode)
|
||||
if bytes.Equal(mauth, hash.Sum(nil)) {
|
||||
return ErrInvalidMessageAuthenticator
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RadiusRequest) setMessageAuthenticator(rp *radius.Packet) error {
|
||||
_ = rfc2869.MessageAuthenticator_Set(rp, make([]byte, 16))
|
||||
hash := hmac.New(md5.New, rp.Secret)
|
||||
encode, err := rp.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash.Write(encode)
|
||||
_ = rfc2869.MessageAuthenticator_Set(rp, hash.Sum(nil))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RadiusRequest) Reject() *radius.Packet {
|
||||
res := r.Response(radius.CodeAccessReject)
|
||||
err := r.setMessageAuthenticator(res)
|
||||
if err != nil {
|
||||
r.log.WithError(err).Warning("failed to set message authenticator")
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) {
|
||||
span := sentry.StartSpan(r.Context(), "authentik.providers.radius.connect",
|
||||
sentry.WithTransactionName("authentik.providers.radius.connect"))
|
||||
@@ -59,6 +103,11 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
|
||||
|
||||
rl.Info("Radius Request")
|
||||
|
||||
if err := nr.validateMessageAuthenticator(); err != nil {
|
||||
rl.WithError(err).Warning("Invalid message authenticator")
|
||||
return
|
||||
}
|
||||
|
||||
// Lookup provider by shared secret
|
||||
var pi *ProviderInstance
|
||||
for _, p := range rs.providers {
|
||||
@@ -72,7 +121,7 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
|
||||
hs := sha512.Sum512([]byte(r.Secret))
|
||||
bs := hex.EncodeToString(hs[:])
|
||||
nr.Log().WithField("hashed_secret", bs).Warning("No provider found")
|
||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||
_ = w.Write(nr.Reject())
|
||||
return
|
||||
}
|
||||
nr.pi = pi
|
||||
|
||||
@@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.6.3
|
||||
Default: 2025.6.4
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
||||
Binary file not shown.
@@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-05 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-07-15 00:11+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||
@@ -2489,6 +2489,14 @@ msgstr "Azure AD OAuth 源"
|
||||
msgid "Azure AD OAuth Sources"
|
||||
msgstr "Azure AD OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Source"
|
||||
msgstr "Entra ID OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Sources"
|
||||
msgstr "Entra ID OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OpenID OAuth Source"
|
||||
msgstr "OpenID OAuth 源"
|
||||
|
||||
@@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-07-05 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-07-15 00:11+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||
@@ -2488,6 +2488,14 @@ msgstr "Azure AD OAuth 源"
|
||||
msgid "Azure AD OAuth Sources"
|
||||
msgstr "Azure AD OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Source"
|
||||
msgstr "Entra ID OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "Entra ID OAuth Sources"
|
||||
msgstr "Entra ID OAuth 源"
|
||||
|
||||
#: authentik/sources/oauth/models.py
|
||||
msgid "OpenID OAuth Source"
|
||||
msgstr "OpenID OAuth 源"
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.3",
|
||||
"version": "2025.6.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.3",
|
||||
"version": "2025.6.4",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.3.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.6.3",
|
||||
"version": "2025.6.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
|
||||
14
packages/docusaurus-config/package-lock.json
generated
14
packages/docusaurus-config/package-lock.json
generated
@@ -5954,9 +5954,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/compression": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
|
||||
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
|
||||
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5964,7 +5964,7 @@
|
||||
"compressible": "~2.0.18",
|
||||
"debug": "2.6.9",
|
||||
"negotiator": "~0.6.4",
|
||||
"on-headers": "~1.0.2",
|
||||
"on-headers": "~1.1.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
@@ -12708,9 +12708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
248
packages/esbuild-plugin-live-reload/package-lock.json
generated
248
packages/esbuild-plugin-live-reload/package-lock.json
generated
@@ -132,9 +132,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
|
||||
"integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -149,9 +149,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
|
||||
"integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -166,9 +166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -183,9 +183,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -200,9 +200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -217,9 +217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -234,9 +234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -251,9 +251,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -268,9 +268,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
|
||||
"integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -285,9 +285,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -302,9 +302,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
|
||||
"integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -319,9 +319,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
|
||||
"integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -336,9 +336,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
|
||||
"integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -353,9 +353,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
|
||||
"integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -370,9 +370,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
|
||||
"integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -387,9 +387,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
|
||||
"integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -404,9 +404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -421,9 +421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -438,9 +438,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -455,9 +455,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -472,9 +472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -489,9 +489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -506,9 +506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -523,9 +523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -540,9 +540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
|
||||
"integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -557,9 +557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -832,9 +832,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
|
||||
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
|
||||
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -904,9 +904,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
|
||||
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1269,9 +1269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
|
||||
"integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -1282,32 +1282,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.6",
|
||||
"@esbuild/android-arm": "0.25.6",
|
||||
"@esbuild/android-arm64": "0.25.6",
|
||||
"@esbuild/android-x64": "0.25.6",
|
||||
"@esbuild/darwin-arm64": "0.25.6",
|
||||
"@esbuild/darwin-x64": "0.25.6",
|
||||
"@esbuild/freebsd-arm64": "0.25.6",
|
||||
"@esbuild/freebsd-x64": "0.25.6",
|
||||
"@esbuild/linux-arm": "0.25.6",
|
||||
"@esbuild/linux-arm64": "0.25.6",
|
||||
"@esbuild/linux-ia32": "0.25.6",
|
||||
"@esbuild/linux-loong64": "0.25.6",
|
||||
"@esbuild/linux-mips64el": "0.25.6",
|
||||
"@esbuild/linux-ppc64": "0.25.6",
|
||||
"@esbuild/linux-riscv64": "0.25.6",
|
||||
"@esbuild/linux-s390x": "0.25.6",
|
||||
"@esbuild/linux-x64": "0.25.6",
|
||||
"@esbuild/netbsd-arm64": "0.25.6",
|
||||
"@esbuild/netbsd-x64": "0.25.6",
|
||||
"@esbuild/openbsd-arm64": "0.25.6",
|
||||
"@esbuild/openbsd-x64": "0.25.6",
|
||||
"@esbuild/openharmony-arm64": "0.25.6",
|
||||
"@esbuild/sunos-x64": "0.25.6",
|
||||
"@esbuild/win32-arm64": "0.25.6",
|
||||
"@esbuild/win32-ia32": "0.25.6",
|
||||
"@esbuild/win32-x64": "0.25.6"
|
||||
"@esbuild/aix-ppc64": "0.25.8",
|
||||
"@esbuild/android-arm": "0.25.8",
|
||||
"@esbuild/android-arm64": "0.25.8",
|
||||
"@esbuild/android-x64": "0.25.8",
|
||||
"@esbuild/darwin-arm64": "0.25.8",
|
||||
"@esbuild/darwin-x64": "0.25.8",
|
||||
"@esbuild/freebsd-arm64": "0.25.8",
|
||||
"@esbuild/freebsd-x64": "0.25.8",
|
||||
"@esbuild/linux-arm": "0.25.8",
|
||||
"@esbuild/linux-arm64": "0.25.8",
|
||||
"@esbuild/linux-ia32": "0.25.8",
|
||||
"@esbuild/linux-loong64": "0.25.8",
|
||||
"@esbuild/linux-mips64el": "0.25.8",
|
||||
"@esbuild/linux-ppc64": "0.25.8",
|
||||
"@esbuild/linux-riscv64": "0.25.8",
|
||||
"@esbuild/linux-s390x": "0.25.8",
|
||||
"@esbuild/linux-x64": "0.25.8",
|
||||
"@esbuild/netbsd-arm64": "0.25.8",
|
||||
"@esbuild/netbsd-x64": "0.25.8",
|
||||
"@esbuild/openbsd-arm64": "0.25.8",
|
||||
"@esbuild/openbsd-x64": "0.25.8",
|
||||
"@esbuild/openharmony-arm64": "0.25.8",
|
||||
"@esbuild/sunos-x64": "0.25.8",
|
||||
"@esbuild/win32-arm64": "0.25.8",
|
||||
"@esbuild/win32-ia32": "0.25.8",
|
||||
"@esbuild/win32-x64": "0.25.8"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
@@ -2328,14 +2328,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-packagejson": {
|
||||
"version": "2.5.18",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
|
||||
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
|
||||
"version": "2.5.19",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.19.tgz",
|
||||
"integrity": "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sort-package-json": "3.4.0",
|
||||
"synckit": "0.11.8"
|
||||
"synckit": "0.11.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": ">= 1.16.0"
|
||||
@@ -2628,13 +2628,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
|
||||
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
|
||||
"version": "0.11.11",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
|
||||
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.4"
|
||||
"@pkgr/core": "^0.2.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
@@ -2728,9 +2728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.0.tgz",
|
||||
"integrity": "sha512-PitbnAps2vpcqK2gargKoiFXLWFttvwUbyns/E6zGIFG5Gz8ZQJGttHnYR9csOlcSjB/uyjd8tnoayrtsXG17w==",
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.1.tgz",
|
||||
"integrity": "sha512-HN/fHLm2S6MD4HX8txfB4eWvVBzX/mEYy5U5s1KTAdh3E5uX5/lilswqTzZlPTT6fNZInAboAdFGpbAuBKnE4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
148
packages/eslint-config/package-lock.json
generated
148
packages/eslint-config/package-lock.json
generated
@@ -237,9 +237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
|
||||
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
|
||||
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
@@ -292,12 +292,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
|
||||
"integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz",
|
||||
"integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.14.0",
|
||||
"@eslint/core": "^0.15.1",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -573,17 +573,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
|
||||
"integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
|
||||
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.37.0",
|
||||
"@typescript-eslint/type-utils": "8.37.0",
|
||||
"@typescript-eslint/utils": "8.37.0",
|
||||
"@typescript-eslint/visitor-keys": "8.37.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/type-utils": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -597,7 +597,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@@ -613,16 +613,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
|
||||
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
|
||||
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.37.0",
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/typescript-estree": "8.37.0",
|
||||
"@typescript-eslint/visitor-keys": "8.37.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -638,14 +638,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
|
||||
"integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
|
||||
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.37.0",
|
||||
"@typescript-eslint/types": "^8.37.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.38.0",
|
||||
"@typescript-eslint/types": "^8.38.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -660,14 +660,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
|
||||
"integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
|
||||
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/visitor-keys": "8.37.0"
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -678,9 +678,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
|
||||
"integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -695,15 +695,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
|
||||
"integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/typescript-estree": "8.37.0",
|
||||
"@typescript-eslint/utils": "8.37.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -720,9 +720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
|
||||
"integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
||||
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -734,16 +734,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
|
||||
"integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
|
||||
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.37.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.37.0",
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/visitor-keys": "8.37.0",
|
||||
"@typescript-eslint/project-service": "8.38.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -802,16 +802,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
|
||||
"integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
|
||||
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.37.0",
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/typescript-estree": "8.37.0"
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -826,13 +826,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
|
||||
"integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
|
||||
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1822,18 +1822,6 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/@eslint/core": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
|
||||
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
@@ -4075,16 +4063,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.37.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
|
||||
"integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz",
|
||||
"integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"@typescript-eslint/typescript-estree": "8.37.0",
|
||||
"@typescript-eslint/utils": "8.37.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
28
packages/prettier-config/package-lock.json
generated
28
packages/prettier-config/package-lock.json
generated
@@ -360,9 +360,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
|
||||
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
|
||||
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -385,9 +385,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
|
||||
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1466,14 +1466,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-packagejson": {
|
||||
"version": "2.5.18",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
|
||||
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
|
||||
"version": "2.5.19",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.19.tgz",
|
||||
"integrity": "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sort-package-json": "3.4.0",
|
||||
"synckit": "0.11.8"
|
||||
"synckit": "0.11.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": ">= 1.16.0"
|
||||
@@ -1657,13 +1657,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
|
||||
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
|
||||
"version": "0.11.11",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
|
||||
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.4"
|
||||
"@pkgr/core": "^0.2.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.6.3"
|
||||
version = "2025.6.4"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.13.*"
|
||||
@@ -8,7 +8,7 @@ dependencies = [
|
||||
"argon2-cffi==25.1.0",
|
||||
"celery==5.5.3",
|
||||
"channels==4.2.2",
|
||||
"channels-redis==4.2.1",
|
||||
"channels-redis==4.3.0",
|
||||
"cryptography==45.0.5",
|
||||
"dacite==1.9.2",
|
||||
"deepmerge==2.0",
|
||||
@@ -44,7 +44,7 @@ dependencies = [
|
||||
"kubernetes==33.1.0",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==6.0.0",
|
||||
"msgraph-sdk==1.38.0",
|
||||
"msgraph-sdk==1.39.0",
|
||||
"opencontainers==0.0.15",
|
||||
"packaging==25.0",
|
||||
"paramiko==3.5.1",
|
||||
@@ -57,7 +57,7 @@ dependencies = [
|
||||
"pyyaml==6.0.2",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.33.0",
|
||||
"sentry-sdk==2.33.2",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.6",
|
||||
"structlog==25.4.0",
|
||||
|
||||
14
schema.yml
14
schema.yml
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.6.3
|
||||
version: 2025.6.4
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@@ -49175,6 +49175,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURI'
|
||||
backchannel_logout_uris:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURI'
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
@@ -49282,6 +49286,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
backchannel_logout_uris:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
@@ -54171,6 +54179,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
backchannel_logout_uris:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectURIRequest'
|
||||
sub_mode:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubModeEnum'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from dataclasses import asdict
|
||||
from time import sleep
|
||||
|
||||
from pyrad.client import Client
|
||||
from pyrad.client import Client, Timeout
|
||||
from pyrad.dictionary import Dictionary
|
||||
from pyrad.packet import AccessAccept, AccessReject, AccessRequest
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestProviderRadius(SeleniumTestCase):
|
||||
"""Start radius container based on outpost created"""
|
||||
self.run_container(
|
||||
image=self.get_container_image("ghcr.io/goauthentik/dev-radius"),
|
||||
ports={"1812/udp": "1812/udp"},
|
||||
ports={"1812/udp": 1812},
|
||||
environment={
|
||||
"AUTHENTIK_TOKEN": outpost.token.key,
|
||||
},
|
||||
@@ -63,7 +63,7 @@ class TestProviderRadius(SeleniumTestCase):
|
||||
sleep(5)
|
||||
return outpost
|
||||
|
||||
@retry()
|
||||
@retry(exceptions=[Timeout])
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
@@ -85,7 +85,7 @@ class TestProviderRadius(SeleniumTestCase):
|
||||
reply = srv.SendPacket(req)
|
||||
self.assertEqual(reply.code, AccessAccept)
|
||||
|
||||
@retry()
|
||||
@retry(exceptions=[Timeout])
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
|
||||
26
uv.lock
generated
26
uv.lock
generated
@@ -165,7 +165,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authentik"
|
||||
version = "2025.6.3"
|
||||
version = "2025.6.4"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "argon2-cffi" },
|
||||
@@ -270,7 +270,7 @@ requires-dist = [
|
||||
{ name = "argon2-cffi", specifier = "==25.1.0" },
|
||||
{ name = "celery", specifier = "==5.5.3" },
|
||||
{ name = "channels", specifier = "==4.2.2" },
|
||||
{ name = "channels-redis", specifier = "==4.2.1" },
|
||||
{ name = "channels-redis", specifier = "==4.3.0" },
|
||||
{ name = "cryptography", specifier = "==45.0.5" },
|
||||
{ name = "dacite", specifier = "==1.9.2" },
|
||||
{ name = "deepmerge", specifier = "==2.0" },
|
||||
@@ -306,7 +306,7 @@ requires-dist = [
|
||||
{ name = "kubernetes", specifier = "==33.1.0" },
|
||||
{ name = "ldap3", specifier = "==2.9.1" },
|
||||
{ name = "lxml", specifier = "==6.0.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.38.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.39.0" },
|
||||
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
||||
{ name = "packaging", specifier = "==25.0" },
|
||||
{ name = "paramiko", specifier = "==3.5.1" },
|
||||
@@ -319,7 +319,7 @@ requires-dist = [
|
||||
{ name = "pyyaml", specifier = "==6.0.2" },
|
||||
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
||||
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.33.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.33.2" },
|
||||
{ name = "service-identity", specifier = "==24.2.0" },
|
||||
{ name = "setproctitle", specifier = "==1.3.6" },
|
||||
{ name = "structlog", specifier = "==25.4.0" },
|
||||
@@ -716,7 +716,7 @@ daphne = [
|
||||
|
||||
[[package]]
|
||||
name = "channels-redis"
|
||||
version = "4.2.1"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
@@ -724,9 +724,9 @@ dependencies = [
|
||||
{ name = "msgpack" },
|
||||
{ name = "redis" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/6d/c379c9feea4522cbdb4eba9b3d23a6270ba8cbd94e847b21834d898109d6/channels_redis-4.2.1.tar.gz", hash = "sha256:8375e81493e684792efe6e6eca60ef3d7782ef76c6664057d2e5c31e80d636dd", size = 31152, upload-time = "2024-11-15T12:58:49.836Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/69/fd3407ad407a80e72ca53850eb7a4c306273e67d5bbb71a86d0e6d088439/channels_redis-4.3.0.tar.gz", hash = "sha256:740ee7b54f0e28cf2264a940a24453d3f00526a96931f911fcb69228ef245dd2", size = 31440, upload-time = "2025-07-22T13:48:46.087Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/aa/981d08ae9627c3b9d8dd150f0fe644122a351abc1f47bcf53d2bfff80d91/channels_redis-4.2.1-py3-none-any.whl", hash = "sha256:2ca33105b3a04b5a327a9c47dd762b546f30b76a0cd3f3f593a23d91d346b6f4", size = 20487, upload-time = "2024-11-15T12:58:47.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/fe/b7224a401ad227b263e5ba84753ffb5a88df048f3b15efd2797903543ce4/channels_redis-4.3.0-py3-none-any.whl", hash = "sha256:48f3e902ae2d5fef7080215524f3b4a1d3cea4e304150678f867a1a822c0d9f5", size = 20641, upload-time = "2025-07-22T13:48:44.545Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2071,7 +2071,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.38.0"
|
||||
version = "1.39.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-identity" },
|
||||
@@ -2081,9 +2081,9 @@ dependencies = [
|
||||
{ name = "microsoft-kiota-serialization-text" },
|
||||
{ name = "msgraph-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/4a/1231a710be05849b8f2c2ce484dcc5a84066bbbc01e8e2c0e7b812a8909c/msgraph_sdk-1.38.0.tar.gz", hash = "sha256:4bb5b30515e64de1e507641f923a348ce83bacb52e2703bb941c144a319c4ca7", size = 6085852, upload-time = "2025-07-17T01:14:36.377Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/a4/fe4f4b2ecb618012b9e8f9ae6dfba1b29f550cc8060d2f871016e37d4f94/msgraph_sdk-1.39.0.tar.gz", hash = "sha256:1769e4628b80635ae688c35e0f97d4f8d38ad84e67c59d0b671ebb0ce56f103a", size = 6086403, upload-time = "2025-07-22T20:40:05.605Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/79/a97b43015afd7112b6f6b0ec1cc65fd06d4f0a9f6737427ba6faf1d5aed4/msgraph_sdk-1.38.0-py3-none-any.whl", hash = "sha256:a6c4725085323152581ed9c9b3433b3f4b6a2e3365a2674354c78a72da8bce9a", size = 24989016, upload-time = "2025-07-17T01:14:32.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/b8/47200dcf7a8fbb1e62c8aa9296566e6d16dea96ff1ccaab0dea8b44e7c86/msgraph_sdk-1.39.0-py3-none-any.whl", hash = "sha256:29b7a2e19d97d9da95d26d9aaa344b47c6b75ac97e5878f0b5cea88451b924e5", size = 24989321, upload-time = "2025-07-22T20:40:02.643Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2964,15 +2964,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.33.0"
|
||||
version = "2.33.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/0b/6139f589436c278b33359845ed77019cd093c41371f898283bbc14d26c02/sentry_sdk-2.33.0.tar.gz", hash = "sha256:cdceed05e186846fdf80ceea261fe0a11ebc93aab2f228ed73d076a07804152e", size = 335233, upload-time = "2025-07-15T12:07:42.413Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/82/dfe4a91fd38e048fbb55ca6c072710408e8802015aa27cde18e8684bb1e9/sentry_sdk-2.33.2.tar.gz", hash = "sha256:e85002234b7b8efac9b74c2d91dbd4f8f3970dc28da8798e39530e65cb740f94", size = 335804, upload-time = "2025-07-22T10:41:18.578Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/93/e5/f24e9f81c9822a24a2627cfcb44c10a3971382e67e5015c6e068421f5787/sentry_sdk-2.33.0-py2.py3-none-any.whl", hash = "sha256:a762d3f19a1c240e16c98796f2a5023f6e58872997d5ae2147ac3ed378b23ec2", size = 356397, upload-time = "2025-07-15T12:07:40.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/4d825d5eb6e924dfcc6a91c8185578a7b0a5c41fd2416a6f49c8226d6ef9/sentry_sdk-2.33.2-py2.py3-none-any.whl", hash = "sha256:8d57a3b4861b243aa9d558fda75509ad487db14f488cbdb6c78c614979d77632", size = 356692, upload-time = "2025-07-22T10:41:16.531Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
1154
web/package-lock.json
generated
1154
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -90,14 +90,14 @@
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/legacy-modes": "^6.5.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@floating-ui/dom": "^1.7.2",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@goauthentik/api": "^2025.6.3-1752500665",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@goauthentik/api": "^2025.6.4-1753191510",
|
||||
"@lit/context": "^1.1.6",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"@lit/task": "^1.0.2",
|
||||
"@lit/reactive-element": "^2.1.1",
|
||||
"@lit/task": "^1.0.3",
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mrmarble/djangoql-completion": "^0.8.3",
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
@@ -105,16 +105,16 @@
|
||||
"@openlayers-elements/maps": "^0.4.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.39.0",
|
||||
"@sentry/browser": "^9.40.0",
|
||||
"@spotlightjs/spotlight": "^3.0.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"change-case": "^5.4.4",
|
||||
"chart.js": "^4.4.9",
|
||||
"chart.js": "^4.5.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"core-js": "^3.42.0",
|
||||
"core-js": "^3.44.0",
|
||||
"country-flag-icons": "^1.5.19",
|
||||
"date-fns": "^4.1.0",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
@@ -122,7 +122,7 @@
|
||||
"fuse.js": "^7.1.0",
|
||||
"guacamole-common-js": "^1.5.0",
|
||||
"hastscript": "^9.0.1",
|
||||
"lit": "^3.2.0",
|
||||
"lit": "^3.3.1",
|
||||
"md-front-matter": "^1.0.4",
|
||||
"mermaid": "^11.9.0",
|
||||
"rapidoc": "^9.3.8",
|
||||
@@ -135,12 +135,12 @@
|
||||
"remark-directive": "^4.0.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-mdx-frontmatter": "^5.0.0",
|
||||
"remark-mdx-frontmatter": "^5.2.0",
|
||||
"style-mod": "^4.1.2",
|
||||
"trusted-types": "^2.0.0",
|
||||
"ts-pattern": "^5.7.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webcomponent-qr-code": "^1.2.0",
|
||||
"webcomponent-qr-code": "^1.3.0",
|
||||
"yaml": "^2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -152,17 +152,17 @@
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@hcaptcha/types": "^1.0.4",
|
||||
"@lit/localize-tools": "^0.8.0",
|
||||
"@storybook/addon-docs": "^9.0.17",
|
||||
"@storybook/addon-links": "^9.0.17",
|
||||
"@storybook/web-components": "^9.0.17",
|
||||
"@storybook/web-components-vite": "^9.0.17",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "^5.60.15",
|
||||
"@storybook/addon-docs": "^9.0.18",
|
||||
"@storybook/addon-links": "^9.0.18",
|
||||
"@storybook/web-components": "^9.0.18",
|
||||
"@storybook/web-components-vite": "^9.0.18",
|
||||
"@types/chart.js": "^4.0.1",
|
||||
"@types/codemirror": "^5.60.16",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/guacamole-common-js": "^1.5.3",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||
@@ -172,7 +172,7 @@
|
||||
"@wdio/spec-reporter": "^9.15.0",
|
||||
"@web/test-runner": "^0.20.2",
|
||||
"chromedriver": "^136.0.3",
|
||||
"esbuild": "^0.25.6",
|
||||
"esbuild": "^0.25.8",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esbuild-plugins-node-modules-polyfill": "^1.7.1",
|
||||
@@ -180,8 +180,8 @@
|
||||
"eslint-plugin-lit": "^2.1.1",
|
||||
"eslint-plugin-wc": "^3.0.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"globals": "^15.10.0",
|
||||
"knip": "^5.58.0",
|
||||
"globals": "^16.3.0",
|
||||
"knip": "^5.62.0",
|
||||
"lit-analyzer": "^2.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -190,8 +190,8 @@
|
||||
"storybook": "^9.0.16",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.37.0",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
"vite-plugin-lit-css": "^2.1.0",
|
||||
"wireit": "^0.14.12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
50
web/packages/core/id/index.js
Normal file
50
web/packages/core/id/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file Unique ID utilities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A global ID generator.
|
||||
*
|
||||
* @singleton
|
||||
* @runtime common
|
||||
*
|
||||
* @category IDs
|
||||
*/
|
||||
export class IDGenerator {
|
||||
static #sequenceIndex = 0;
|
||||
static #elementIndex = 0;
|
||||
|
||||
/**
|
||||
* Create a new ID for an HTML element.
|
||||
*
|
||||
* This ID will be unique for the lifetime of the page and will not be
|
||||
* exposed on the `window` object.
|
||||
*
|
||||
* @param {string | number} [name] An optional name to use for the element.
|
||||
*/
|
||||
static elementID(name) {
|
||||
name = name || ++this.#elementIndex;
|
||||
|
||||
return "«ak-" + name + "»";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ID.
|
||||
*/
|
||||
static next() {
|
||||
this.#sequenceIndex += 1;
|
||||
|
||||
return this.#sequenceIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random ID in hexadecimal format.
|
||||
*
|
||||
* @param {number} [characterLength]
|
||||
*/
|
||||
static randomID(characterLength = 6) {
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(characterLength / 2));
|
||||
|
||||
return Array.from(bytes, (a) => a.toString(16)).join("");
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
"devDependencies": {
|
||||
"@goauthentik/prettier-config": "^1.0.5",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/node": "^24.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
|
||||
27
web/packages/core/promises/index.js
Normal file
27
web/packages/core/promises/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file Helpers for running tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function that returns a promise.
|
||||
* @template {never[]} [A=never[]]
|
||||
* @typedef {(...args: A) => Promise<unknown>} Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* A tuple of a function and its arguments.
|
||||
* @template {Thenable} [T=Thenable]
|
||||
* @typedef {[T, Parameters<T>]} SerializedThenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes a sequence of promise-returning functions in series
|
||||
* @template {Thenable[]} T
|
||||
* @param {{ [K in keyof T]: [T[K], ...Parameters<T[K]>] }} sequence
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function series(...sequence) {
|
||||
for (const [thenable, ...args] of sequence) {
|
||||
await thenable(...args);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"@goauthentik/api": "^2024.6.0-1719577139",
|
||||
"base64-js": "^1.5.1",
|
||||
"bootstrap": "^4.6.1",
|
||||
"bootstrap": "^5.3.7",
|
||||
"formdata-polyfill": "^4.0.10",
|
||||
"jquery": "^3.7.1",
|
||||
"weakmap-polyfill": "^2.0.4"
|
||||
@@ -23,7 +23,7 @@
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-swc": "^0.4.0",
|
||||
"@swc/cli": "^0.7.8",
|
||||
"@swc/core": "^1.12.14",
|
||||
"@swc/core": "^1.13.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"rollup": "^4.45.1",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthent
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
|
||||
@@ -56,21 +57,32 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
];
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
#contentRef = createRef<HTMLDivElement>();
|
||||
|
||||
#backdropListener = (event: PointerEvent) => {
|
||||
// We only want to close the modal when the backdrop is clicked, not when it's children are clicked.
|
||||
|
||||
if (this.#contentRef.value?.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
|
||||
protected override renderModal() {
|
||||
let product = this.brandingTitle;
|
||||
|
||||
if (this.licenseSummary.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
||||
if (this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
return html`<div
|
||||
class="pf-c-backdrop"
|
||||
@click=${(e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
this.closeModal();
|
||||
}}
|
||||
>
|
||||
return html`<div class="pf-c-backdrop" @click=${this.#backdropListener}>
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-about-modal-box" role="dialog" aria-modal="true">
|
||||
<div
|
||||
${ref(this.#contentRef)}
|
||||
class="pf-c-about-modal-box"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<div class="pf-c-about-modal-box__brand">
|
||||
<img
|
||||
class="pf-c-about-modal-box__brand-image"
|
||||
@@ -79,18 +91,12 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__close">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.open = false;
|
||||
}}
|
||||
>
|
||||
<button class="pf-c-button pf-m-plain" type="button" @click=${this.close}>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-4xl">${product}</h1>
|
||||
<h1 class="pf-c-title pf-m-4xl" id="modal-title">${product}</h1>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__hero"></div>
|
||||
<div class="pf-c-about-modal-box__content">
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "#elements/router/Route";
|
||||
import { SidebarItemProperties } from "#elements/sidebar/SidebarItem";
|
||||
import { LitPropertyRecord } from "#elements/types";
|
||||
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
@@ -11,7 +14,7 @@ import { repeat } from "lit/directives/repeat.js";
|
||||
type SidebarEntry = [
|
||||
path: string | null,
|
||||
label: string,
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
attributes?: LitPropertyRecord<SidebarItemProperties> | string[] | null,
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
|
||||
@@ -32,8 +35,7 @@ export function renderSidebarItem([
|
||||
properties.path = path;
|
||||
}
|
||||
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
return html`<ak-sidebar-item label=${ifDefined(label)} ${spread(properties)}>
|
||||
${children ? renderSidebarItems(children) : nothing}
|
||||
</ak-sidebar-item>`;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { ROUTES } from "#admin/Routes";
|
||||
|
||||
import { CapabilitiesEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
@@ -163,16 +164,18 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
}
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.user = await me();
|
||||
me().then((session) => {
|
||||
this.user = session;
|
||||
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
const canAccessAdmin =
|
||||
this.user.user.isSuperuser ||
|
||||
// TODO: somehow add `access_admin_interface` to the API schema
|
||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||
window.location.assign("/if/user/");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@@ -191,13 +194,14 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
};
|
||||
|
||||
return html` <ak-locale-context>
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<div class="pf-c-page">
|
||||
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
</ak-page-navbar>
|
||||
|
||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
||||
<ak-sidebar ?hidden=${!this.sidebarOpen} class="${classMap(sidebarClasses)}">
|
||||
${renderSidebarItems(AdminSidebarEntries)}
|
||||
${this.can(CapabilitiesEnum.IsEnterprise)
|
||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
||||
@@ -209,9 +213,10 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
<div class="pf-c-drawer__main">
|
||||
<div class="pf-c-drawer__content">
|
||||
<div class="pf-c-drawer__body">
|
||||
<main class="pf-c-page__main">
|
||||
<div class="pf-c-page__main">
|
||||
<ak-router-outlet
|
||||
role="main"
|
||||
aria-label="${msg("Main content")}"
|
||||
class="pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
@@ -219,7 +224,7 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
||||
.routes=${ROUTES}
|
||||
>
|
||||
</ak-router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ak-notification-drawer
|
||||
|
||||
@@ -3,14 +3,15 @@ import { PFSize } from "#common/enums";
|
||||
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
||||
|
||||
import { AggregateCard } from "#elements/cards/AggregateCard";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { html, nothing, PropertyValues } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
|
||||
export interface AdminStatus {
|
||||
icon: string;
|
||||
message?: TemplateResult;
|
||||
message?: SlottedTemplateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,8 +96,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult displaying the value
|
||||
*/
|
||||
protected renderValue(): TemplateResult {
|
||||
return html`${this.value}`;
|
||||
protected renderValue(): SlottedTemplateResult {
|
||||
return this.value ? html`${this.value}` : nothing;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +106,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
* @param status - AdminStatus object containing icon and message
|
||||
* @returns TemplateResult for status display
|
||||
*/
|
||||
private renderStatus(status: AdminStatus): TemplateResult {
|
||||
private renderStatus(status: AdminStatus): SlottedTemplateResult {
|
||||
return html`
|
||||
<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
||||
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
|
||||
@@ -118,9 +119,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
* @param error - Error message to display
|
||||
* @returns TemplateResult for error display
|
||||
*/
|
||||
private renderError(error: string): TemplateResult {
|
||||
private renderError(error: string): SlottedTemplateResult {
|
||||
return html`
|
||||
<p><i class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||
<p><i aria-hidden="true" class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||
<p class="subtext">${error}</p>
|
||||
`;
|
||||
}
|
||||
@@ -130,7 +131,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult for loading spinner
|
||||
*/
|
||||
private renderLoading(): TemplateResult {
|
||||
private renderLoading(): SlottedTemplateResult {
|
||||
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
|
||||
}
|
||||
|
||||
@@ -139,7 +140,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*
|
||||
* @returns TemplateResult for current component state
|
||||
*/
|
||||
renderInner(): TemplateResult {
|
||||
renderInner(): SlottedTemplateResult {
|
||||
return html`
|
||||
<p class="center-value">
|
||||
${
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { AdminStatus, AdminStatusCard } from "#admin/admin-overview/cards/AdminStatusCard";
|
||||
|
||||
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-admin-status-system")
|
||||
@@ -82,12 +84,12 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
|
||||
});
|
||||
}
|
||||
|
||||
renderHeader(): TemplateResult {
|
||||
return html`${msg("System status")}`;
|
||||
renderHeader(): SlottedTemplateResult {
|
||||
return msg("System status");
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${this.statusSummary}`;
|
||||
renderValue(): SlottedTemplateResult {
|
||||
return this.statusSummary ? html`${this.statusSummary}` : nothing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,9 +179,8 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
.options=${policyEngineModes}
|
||||
.value=${this.instance?.policyEngineMode}
|
||||
></ak-radio-input>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("UI settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("UI settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
|
||||
@@ -84,7 +84,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
];
|
||||
}
|
||||
|
||||
renderSidebarAfter(): TemplateResult {
|
||||
protected renderSidebarAfter(): TemplateResult {
|
||||
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
|
||||
@@ -149,9 +149,8 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
.value=${app.policyEngineMode}
|
||||
.errorMessages=${errors.policyEngineMode ?? []}
|
||||
></ak-radio-input>
|
||||
<ak-form-group aria-label=${msg("UI Settings")}>
|
||||
<span slot="header"> ${msg("UI Settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label=${msg("UI Settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="metaLaunchUrl"
|
||||
label=${msg("Launch URL")}
|
||||
|
||||
@@ -61,9 +61,8 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
|
||||
@@ -177,9 +177,8 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Additional settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Additional settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
|
||||
@@ -91,9 +91,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Branding settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Branding settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
|
||||
<input
|
||||
type="text"
|
||||
@@ -174,9 +173,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("External user settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("External user settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Default application")}
|
||||
name="defaultApplication"
|
||||
@@ -219,9 +217,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Default flows")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Default flows")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="flowAuthentication"
|
||||
@@ -299,9 +296,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Other global settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Other global settings")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Web Certificate")}
|
||||
name="webCertificate"
|
||||
|
||||
@@ -214,9 +214,8 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
${msg("Required authentication level for this flow.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Behavior settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Behavior settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="compatibilityMode">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@@ -289,9 +288,8 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Appearance settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Appearance settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
|
||||
@@ -234,9 +234,8 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||
selected-label="${msg("Selected Applications")}"
|
||||
></ak-dual-select-provider>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group aria-label=${msg("Advanced settings")}>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Configuration")} name="config">
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
|
||||
@@ -66,9 +66,8 @@ export class DummyPolicyForm extends BasePolicyForm<DummyPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="result">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@@ -78,9 +78,8 @@ export class EventMatcherPolicyForm extends BasePolicyForm<EventMatcherPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Action")} name="action">
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {
|
||||
|
||||
@@ -66,9 +66,8 @@ export class PasswordExpiryPolicyForm extends BasePolicyForm<PasswordExpiryPolic
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Maximum age (in days)")}
|
||||
required
|
||||
|
||||
@@ -70,9 +70,8 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Expression")}
|
||||
required
|
||||
|
||||
@@ -81,9 +81,8 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Distance settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Distance settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="checkHistoryDistance">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@@ -188,9 +187,8 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Static rule settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Static rule settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("ASNs")} name="asns">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -46,9 +46,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
}
|
||||
|
||||
renderStaticRules(): TemplateResult {
|
||||
return html` <ak-form-group>
|
||||
<span slot="header"> ${msg("Static rules")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
return html` <ak-form-group label="${msg("Static rules")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Minimum length")}
|
||||
required
|
||||
@@ -144,9 +143,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
|
||||
renderHIBP(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("HaveIBeenPwned settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("HaveIBeenPwned settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Allowed count")}
|
||||
required
|
||||
@@ -169,9 +167,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
||||
|
||||
renderZxcvbn(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("zxcvbn settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("zxcvbn settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Score threshold")}
|
||||
required
|
||||
|
||||
@@ -76,9 +76,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="checkIp">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@@ -65,9 +65,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("General settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("General settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Username")}
|
||||
name="staticSettings.username"
|
||||
@@ -92,9 +91,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("RDP settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("RDP settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Ignore server certificate")}
|
||||
name="staticSettings.ignore-cert"
|
||||
@@ -137,9 +135,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Expression")}
|
||||
required
|
||||
|
||||
@@ -59,9 +59,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Credentials")}
|
||||
required
|
||||
@@ -184,9 +183,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("User filtering")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@@ -237,9 +235,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
|
||||
@@ -48,6 +48,7 @@ export function renderForm(
|
||||
return html`
|
||||
<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Provider name")}
|
||||
value=${ifDefined(provider?.name)}
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
@@ -80,10 +81,8 @@ export function renderForm(
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Flow settings")} </span>
|
||||
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Bind flow")}
|
||||
required
|
||||
@@ -91,6 +90,7 @@ export function renderForm(
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-branded-flow-search
|
||||
label=${msg("Bind flow")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
.brandFlow=${brand?.flowAuthentication}
|
||||
@@ -119,9 +119,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="baseDn"
|
||||
label=${msg("Base DN")}
|
||||
|
||||
@@ -57,9 +57,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
||||
<input
|
||||
type="text"
|
||||
@@ -160,9 +159,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("User filtering")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
@@ -213,9 +211,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { AkControlElement } from "#elements/AkControlElement";
|
||||
import { type Spread } from "#elements/types";
|
||||
|
||||
import { MatchingModeEnum, RedirectURI } from "@goauthentik/api";
|
||||
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property, queryAll } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IBackchannelLogoutURIInput {
|
||||
backchannelLogoutURI: RedirectURI;
|
||||
}
|
||||
|
||||
@customElement("ak-provider-oauth2-backchannel-logout-uri")
|
||||
export class OAuth2ProviderBackchannelLogoutURI extends AkControlElement<RedirectURI> {
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFInputGroup,
|
||||
PFFormControl,
|
||||
css`
|
||||
.pf-c-input-group select {
|
||||
width: 10em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
backchannelLogoutURI: RedirectURI = {
|
||||
matchingMode: MatchingModeEnum.Strict,
|
||||
url: "",
|
||||
};
|
||||
|
||||
@queryAll(".ak-form-control")
|
||||
controls?: HTMLInputElement[];
|
||||
|
||||
json() {
|
||||
return Object.fromEntries(
|
||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
||||
) as unknown as RedirectURI;
|
||||
}
|
||||
|
||||
get isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const onChange = () => {
|
||||
this.dispatchEvent(new Event("change", { composed: true, bubbles: true }));
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-input-group">
|
||||
<select
|
||||
name="matchingMode"
|
||||
class="pf-c-form-control ak-form-control"
|
||||
@change=${onChange}
|
||||
>
|
||||
<option
|
||||
value="${MatchingModeEnum.Strict}"
|
||||
?selected=${this.backchannelLogoutURI.matchingMode === MatchingModeEnum.Strict}
|
||||
>
|
||||
${msg("Strict")}
|
||||
</option>
|
||||
<option
|
||||
value="${MatchingModeEnum.Regex}"
|
||||
?selected=${this.backchannelLogoutURI.matchingMode === MatchingModeEnum.Regex}
|
||||
>
|
||||
${msg("Regex")}
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
@change=${onChange}
|
||||
value="${ifDefined(this.backchannelLogoutURI.url ?? undefined)}"
|
||||
class="pf-c-form-control ak-form-control pf-m-monospace"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
id="url"
|
||||
placeholder=${msg("Back-Channel Logout URL")}
|
||||
name="url"
|
||||
tabindex="1"
|
||||
/>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function akOAuthBackchannelLogoutURIInput(properties: IBackchannelLogoutURIInput) {
|
||||
return html`<ak-provider-oauth2-backchannel-logout-uri
|
||||
${spread(properties as unknown as Spread)}
|
||||
></ak-provider-oauth2-backchannel-logout-uri>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-provider-oauth2-backchannel-logout-uri": OAuth2ProviderBackchannelLogoutURI;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/Radio";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
import "#elements/utils/TimeDeltaHelp";
|
||||
import "#admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
|
||||
import { propertyMappingsProvider, propertyMappingsSelector } from "./OAuth2ProviderFormHelpers.js";
|
||||
import { oauth2ProvidersProvider, oauth2ProvidersSelector } from "./OAuth2ProvidersProvider.js";
|
||||
@@ -20,9 +21,9 @@ import { oauth2SourcesProvider, oauth2SourcesSelector } from "./OAuth2Sources.js
|
||||
import { ascii_letters, digits, randomString } from "#common/utils";
|
||||
|
||||
import {
|
||||
akOAuthRedirectURIInput,
|
||||
IRedirectURIInput,
|
||||
} from "#admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
akOAuthBackchannelLogoutURIInput,
|
||||
IBackchannelLogoutURIInput,
|
||||
} from "#admin/providers/oauth2/OAuth2ProviderBackchannelLogoutURI";
|
||||
|
||||
import {
|
||||
ClientTypeEnum,
|
||||
@@ -117,6 +118,19 @@ export const redirectUriHelp = html`${redirectUriHelpMessages.map(
|
||||
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
||||
)}`;
|
||||
|
||||
const backchannelLogoutUriHelpMessages = [
|
||||
msg(
|
||||
"URIs to send back-channel logout notifications to when users log out. Required for OpenID Connect Back-Channel Logout functionality.",
|
||||
),
|
||||
msg(
|
||||
"These URIs are called server-to-server when a user logs out to notify OAuth2/OpenID clients about the logout event.",
|
||||
),
|
||||
];
|
||||
|
||||
export const backchannelLogoutUriHelp = html`${backchannelLogoutUriHelpMessages.map(
|
||||
(m) => html`<p class="pf-c-form__helper-text">${m}</p>`,
|
||||
)}`;
|
||||
|
||||
type ShowClientSecret = (show: boolean) => void;
|
||||
const defaultShowClientSecret: ShowClientSecret = (_show) => undefined;
|
||||
|
||||
@@ -147,9 +161,8 @@ export function renderForm(
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-radio-input
|
||||
name="clientType"
|
||||
label=${msg("Client type")}
|
||||
@@ -179,22 +192,42 @@ export function renderForm(
|
||||
>
|
||||
</ak-hidden-text-input>
|
||||
<ak-form-element-horizontal
|
||||
flow-direction="row"
|
||||
label=${msg("Redirect URIs/Origins (RegEx)")}
|
||||
name="redirectUris"
|
||||
>
|
||||
<ak-array-input
|
||||
.items=${provider?.redirectUris ?? []}
|
||||
.newItem=${() => ({ matchingMode: MatchingModeEnum.Strict, url: "" })}
|
||||
.row=${(f?: RedirectURI) =>
|
||||
akOAuthRedirectURIInput({
|
||||
".redirectURI": f,
|
||||
"style": "width: 100%",
|
||||
"name": "oauth2-redirect-uri",
|
||||
} as unknown as IRedirectURIInput)}
|
||||
.row=${(redirectURI: RedirectURI, idx: number) => {
|
||||
return html`<ak-provider-oauth2-redirect-uri
|
||||
.redirectURI=${redirectURI}
|
||||
name="oauth2-redirect-uri"
|
||||
style="width: 100%"
|
||||
inputID="redirect-uri-${idx}"
|
||||
></ak-provider-oauth2-redirect-uri>`;
|
||||
}}
|
||||
>
|
||||
</ak-array-input>
|
||||
${redirectUriHelp}
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Back-Channel Logout URIs")}
|
||||
name="backchannelLogoutUris"
|
||||
>
|
||||
<ak-array-input
|
||||
.items=${provider?.backchannelLogoutUris ?? []}
|
||||
.newItem=${() => ({ matchingMode: MatchingModeEnum.Strict, url: "" })}
|
||||
.row=${(f?: RedirectURI) =>
|
||||
akOAuthBackchannelLogoutURIInput({
|
||||
".backchannelLogoutURI": f,
|
||||
"style": "width: 100%",
|
||||
"name": "oauth2-backchannel-logout-uri",
|
||||
} as unknown as IBackchannelLogoutURIInput)}
|
||||
>
|
||||
</ak-array-input>
|
||||
${backchannelLogoutUriHelp}
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||
@@ -214,9 +247,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label=${msg("Advanced flow settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
@@ -249,9 +281,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="accessCodeValidity"
|
||||
label=${msg("Access code validity")}
|
||||
@@ -334,9 +365,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Machine-to-Machine authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Federated OIDC Sources")}
|
||||
name="jwtFederationSources"
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import "#admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||
|
||||
import { AkControlElement } from "#elements/AkControlElement";
|
||||
import { type Spread } from "#elements/types";
|
||||
import { LitPropertyRecord } from "#elements/types";
|
||||
|
||||
import { MatchingModeEnum, RedirectURI } from "@goauthentik/api";
|
||||
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property, queryAll } from "lit/decorators.js";
|
||||
@@ -16,9 +14,12 @@ import PFFormControl from "@patternfly/patternfly/components/FormControl/form-co
|
||||
import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IRedirectURIInput {
|
||||
export type RedirectURIProperties = LitPropertyRecord<{
|
||||
redirectURI: RedirectURI;
|
||||
}
|
||||
}> & {
|
||||
style?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
@customElement("ak-provider-oauth2-redirect-uri")
|
||||
export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
||||
@@ -34,11 +35,14 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
||||
];
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
redirectURI: RedirectURI = {
|
||||
public redirectURI: RedirectURI = {
|
||||
matchingMode: MatchingModeEnum.Strict,
|
||||
url: "",
|
||||
};
|
||||
|
||||
@property({ type: String })
|
||||
public inputID?: string;
|
||||
|
||||
@queryAll(".ak-form-control")
|
||||
controls?: HTMLInputElement[];
|
||||
|
||||
@@ -84,7 +88,7 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
required
|
||||
id="url"
|
||||
id=${ifDefined(this.inputID)}
|
||||
placeholder=${msg("URL")}
|
||||
name="url"
|
||||
tabindex="1"
|
||||
@@ -93,12 +97,6 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
||||
}
|
||||
}
|
||||
|
||||
export function akOAuthRedirectURIInput(properties: IRedirectURIInput) {
|
||||
return html`<ak-provider-oauth2-redirect-uri
|
||||
${spread(properties as unknown as Spread)}
|
||||
></ak-provider-oauth2-redirect-uri>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-provider-oauth2-redirect-uri": OAuth2ProviderRedirectURI;
|
||||
|
||||
@@ -246,6 +246,24 @@ export class OAuth2ProviderViewPage extends AKElement {
|
||||
</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("Back-Channel Logout URIs")}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ul>
|
||||
${this.provider.backchannelLogoutUris?.map((ru) => {
|
||||
return html`<li class="pf-m-monospace">
|
||||
${ru.matchingMode}: ${ru.url}
|
||||
</li>`;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
|
||||
@@ -230,9 +230,8 @@ export function renderForm(
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.certificate}
|
||||
@@ -275,9 +274,8 @@ ${provider?.skipPathRegex}</textarea
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="interceptHeaderAuth"
|
||||
label=${msg("Intercept header authentication")}
|
||||
@@ -335,9 +333,8 @@ ${provider?.skipPathRegex}</textarea
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
|
||||
@@ -118,9 +118,8 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
|
||||
selected-label="${msg("Selected User Property Mappings")}"
|
||||
></ak-dual-select-dynamic-selected>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
|
||||
@@ -118,9 +118,8 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
|
||||
@@ -74,9 +74,8 @@ export function renderForm(
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-hidden-text-input
|
||||
name="sharedSecret"
|
||||
label=${msg("Shared secret")}
|
||||
@@ -107,15 +106,16 @@ export function renderForm(
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Invalidation flow")}
|
||||
placeholder=${msg("Select an invalidation flow...")}
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
.errorMessages=${errors?.invalidationFlow ?? []}
|
||||
|
||||
@@ -85,9 +85,8 @@ export function renderForm(
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="acsUrl"
|
||||
label=${msg("ACS URL")}
|
||||
@@ -123,9 +122,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
@@ -158,9 +156,8 @@ export function renderForm(
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.signingKp}
|
||||
|
||||
@@ -32,9 +32,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
required
|
||||
help=${msg("Method's display Name.")}
|
||||
></ak-text-input>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="url"
|
||||
label=${msg("URL")}
|
||||
@@ -114,9 +113,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header">${msg("User filtering")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("User filtering")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="excludeUsersServiceAccount"
|
||||
label=${msg("Exclude service accounts")}
|
||||
@@ -156,9 +154,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="propertyMappings"
|
||||
|
||||
@@ -59,9 +59,8 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
|
||||
value=${ifDefined(provider?.name)}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signing Key")}
|
||||
name="signingKey"
|
||||
@@ -95,9 +94,8 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${msg("Authentication settings")}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("OIDC Providers")}
|
||||
name="oidcAuthProviders"
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TablePage } from "#elements/table/TablePage";
|
||||
import { InitialPermissions, RbacApi } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html, HTMLTemplateResult, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@@ -68,7 +68,7 @@ export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render(): HTMLTemplateResult {
|
||||
return html`<ak-page-header
|
||||
icon=${this.pageIcon()}
|
||||
header=${this.pageTitle()}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TablePage } from "#elements/table/TablePage";
|
||||
import { RbacApi, Role } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html, HTMLTemplateResult, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@@ -66,7 +66,7 @@ export class RoleListPage extends TablePage<Role> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render(): HTMLTemplateResult {
|
||||
return html`<ak-page-header
|
||||
icon=${this.pageIcon()}
|
||||
header=${this.pageTitle()}
|
||||
|
||||
@@ -121,9 +121,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
"Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Realm settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Realm settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="realm"
|
||||
label=${msg("Realm")}
|
||||
@@ -213,9 +212,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Sync connection settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Sync connection settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("KAdmin type")}
|
||||
required
|
||||
@@ -276,9 +274,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("SPNEGO settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("SPNEGO settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="spnegoServerName"
|
||||
label=${msg("SPNEGO server name")}
|
||||
@@ -305,9 +302,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Kerberos Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Kerberos Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="userPropertyMappings"
|
||||
@@ -344,9 +340,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
@@ -377,9 +372,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Additional settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Additional settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="userPathTemplate"
|
||||
label=${msg("User path")}
|
||||
|
||||
@@ -173,9 +173,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Connection settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Connection settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Server URI")}
|
||||
required
|
||||
@@ -279,9 +278,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("LDAP Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("LDAP Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="userPropertyMappings"
|
||||
@@ -316,9 +314,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Additional settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Additional settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup">
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||
|
||||
@@ -126,9 +126,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
if (!this.providerType?.urlsCustomizable) {
|
||||
return html``;
|
||||
}
|
||||
return html` <ak-form-group expanded>
|
||||
<span slot="header"> ${msg("URL settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
return html` <ak-form-group open label="${msg("URL settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization URL")}
|
||||
name="authorizationUrl"
|
||||
@@ -421,9 +420,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
||||
</ak-form-element-horizontal>`}
|
||||
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Consumer key")}
|
||||
required
|
||||
@@ -464,9 +462,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
</div>
|
||||
</ak-form-group>
|
||||
${this.renderUrlOptions()}
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("OAuth Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("OAuth Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="userPropertyMappings"
|
||||
@@ -501,9 +498,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
|
||||
@@ -333,9 +333,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
||||
</ak-form-element-horizontal>`}
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Protocol settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
||||
<input
|
||||
type="text"
|
||||
@@ -347,9 +346,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
||||
${this.renderSettings()}
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group label="${msg("Flow settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
name="authenticationFlow"
|
||||
@@ -380,9 +378,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Plex Attribute mapping")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-group open label="${msg("Plex Attribute mapping")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("User Property Mappings")}
|
||||
name="userPropertyMappings"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user