Compare commits
65 Commits
permission
...
tests/e2e/
Author | SHA1 | Date | |
---|---|---|---|
25181d079e | |||
5b91cb5ff3 | |||
8ba5fde5ba | |||
2802deb497 | |||
3eccef88aa | |||
8f50dfa0c5 | |||
8417d8508f | |||
b2c2fc001b | |||
f60312cbbc | |||
7614b17a05 | |||
8947376edb | |||
ce23209ae8 | |||
0b806b7130 | |||
9538cf4690 | |||
63da458fb3 | |||
873dab29a9 | |||
1e96c80593 | |||
ee4a922234 | |||
37a2eff716 | |||
50e2f1c474 | |||
ab7338b50e | |||
bcdc6fcd36 | |||
98c3e0d68b | |||
a2b82b6448 | |||
0456ace646 | |||
d3a11ce810 | |||
bfd1445c69 | |||
c2b3e9b05c | |||
2c7d841e4a | |||
c5d13c4a15 | |||
079ef6e114 | |||
98bfca0b4d | |||
a247bd5b9f | |||
27856ec301 | |||
e4a8c05d25 | |||
cb2e0c6d54 | |||
f37e1ca642 | |||
70b1f05a84 | |||
192ed8f494 | |||
b69d77d270 | |||
35b6801ba0 | |||
f9e6f57aad | |||
868261c883 | |||
b6442c233d | |||
74292e6c23 | |||
3e2cf4fd30 | |||
05cbb4ce0c | |||
c93d85731c | |||
d163afe87c | |||
eac2c9a12b | |||
c10e4a9063 | |||
4e4adcc672 | |||
bb20576d84 | |||
5f315bddbd | |||
9e0404646b | |||
45883ff86b | |||
915f5689c6 | |||
ce1ea926f8 | |||
2e3624ea82 | |||
4e52fb7e52 | |||
7e36fb2153 | |||
2b00754324 | |||
12a73ef306 | |||
4469db9b23 | |||
b7beac6795 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.2.1
|
current_version = 2025.2.2
|
||||||
tag = True
|
tag = True
|
||||||
commit = 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*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
|
16
.github/actions/setup/action.yml
vendored
16
.github/actions/setup/action.yml
vendored
@ -9,17 +9,22 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install poetry & deps
|
- name: Install apt deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pipx install poetry || true
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
|
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
|
||||||
- name: Setup python and restore poetry
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
- name: Setup python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version-file: "pyproject.toml"
|
python-version-file: "pyproject.toml"
|
||||||
cache: "poetry"
|
- name: Install Python deps
|
||||||
|
shell: bash
|
||||||
|
run: uv sync --all-extras --dev --frozen
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@ -39,10 +44,9 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
export PSQL_TAG=${{ inputs.postgresql_version }}
|
export PSQL_TAG=${{ inputs.postgresql_version }}
|
||||||
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
||||||
poetry sync
|
|
||||||
cd web && npm ci
|
cd web && npm ci
|
||||||
- name: Generate config
|
- name: Generate config
|
||||||
shell: poetry run python {0}
|
shell: uv run python {0}
|
||||||
run: |
|
run: |
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from yaml import safe_dump
|
from yaml import safe_dump
|
||||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -98,7 +98,7 @@ updates:
|
|||||||
prefix: "lifecycle/aws:"
|
prefix: "lifecycle/aws:"
|
||||||
labels:
|
labels:
|
||||||
- dependencies
|
- dependencies
|
||||||
- package-ecosystem: pip
|
- package-ecosystem: uv
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
2
.github/workflows/ci-aws-cfn.yml
vendored
2
.github/workflows/ci-aws-cfn.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
- name: Check changes have been applied
|
- name: Check changes have been applied
|
||||||
run: |
|
run: |
|
||||||
poetry run make aws-cfn
|
uv run make aws-cfn
|
||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
ci-aws-cfn-mark:
|
ci-aws-cfn-mark:
|
||||||
if: always()
|
if: always()
|
||||||
|
32
.github/workflows/ci-main.yml
vendored
32
.github/workflows/ci-main.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Setup authentik env
|
- name: Setup authentik env
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
- name: run job
|
- name: run job
|
||||||
run: poetry run make ci-${{ matrix.job }}
|
run: uv run make ci-${{ matrix.job }}
|
||||||
test-migrations:
|
test-migrations:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Setup authentik env
|
- name: Setup authentik env
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
- name: run migrations
|
- name: run migrations
|
||||||
run: poetry run python -m lifecycle.migrate
|
run: uv run python -m lifecycle.migrate
|
||||||
test-make-seed:
|
test-make-seed:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -69,19 +69,21 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: checkout stable
|
- name: checkout stable
|
||||||
run: |
|
run: |
|
||||||
# Delete all poetry envs
|
|
||||||
rm -rf /home/runner/.cache/pypoetry
|
|
||||||
# Copy current, latest config to local
|
# Copy current, latest config to local
|
||||||
|
# Temporarly comment the .github backup while migrating to uv
|
||||||
cp authentik/lib/default.yml local.env.yml
|
cp authentik/lib/default.yml local.env.yml
|
||||||
cp -R .github ..
|
# cp -R .github ..
|
||||||
cp -R scripts ..
|
cp -R scripts ..
|
||||||
git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
|
git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
|
||||||
rm -rf .github/ scripts/
|
# rm -rf .github/ scripts/
|
||||||
mv ../.github ../scripts .
|
# mv ../.github ../scripts .
|
||||||
|
rm -rf scripts/
|
||||||
|
mv ../scripts .
|
||||||
- name: Setup authentik env (stable)
|
- name: Setup authentik env (stable)
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
postgresql_version: ${{ matrix.psql }}
|
postgresql_version: ${{ matrix.psql }}
|
||||||
|
continue-on-error: true
|
||||||
- name: run migrations to stable
|
- name: run migrations to stable
|
||||||
run: poetry run python -m lifecycle.migrate
|
run: poetry run python -m lifecycle.migrate
|
||||||
- name: checkout current code
|
- name: checkout current code
|
||||||
@ -91,15 +93,13 @@ jobs:
|
|||||||
git reset --hard HEAD
|
git reset --hard HEAD
|
||||||
git clean -d -fx .
|
git clean -d -fx .
|
||||||
git checkout $GITHUB_SHA
|
git checkout $GITHUB_SHA
|
||||||
# Delete previous poetry env
|
|
||||||
rm -rf /home/runner/.cache/pypoetry/virtualenvs/*
|
|
||||||
- name: Setup authentik env (ensure latest deps are installed)
|
- name: Setup authentik env (ensure latest deps are installed)
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
postgresql_version: ${{ matrix.psql }}
|
postgresql_version: ${{ matrix.psql }}
|
||||||
- name: migrate to latest
|
- name: migrate to latest
|
||||||
run: |
|
run: |
|
||||||
poetry run python -m lifecycle.migrate
|
uv run python -m lifecycle.migrate
|
||||||
- name: run tests
|
- name: run tests
|
||||||
env:
|
env:
|
||||||
# Test in the main database that we just migrated from the previous stable version
|
# Test in the main database that we just migrated from the previous stable version
|
||||||
@ -108,7 +108,7 @@ jobs:
|
|||||||
CI_RUN_ID: ${{ matrix.run_id }}
|
CI_RUN_ID: ${{ matrix.run_id }}
|
||||||
CI_TOTAL_RUNS: "5"
|
CI_TOTAL_RUNS: "5"
|
||||||
run: |
|
run: |
|
||||||
poetry run make ci-test
|
uv run make ci-test
|
||||||
test-unittest:
|
test-unittest:
|
||||||
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -133,7 +133,7 @@ jobs:
|
|||||||
CI_RUN_ID: ${{ matrix.run_id }}
|
CI_RUN_ID: ${{ matrix.run_id }}
|
||||||
CI_TOTAL_RUNS: "5"
|
CI_TOTAL_RUNS: "5"
|
||||||
run: |
|
run: |
|
||||||
poetry run make ci-test
|
uv run make ci-test
|
||||||
- if: ${{ always() }}
|
- if: ${{ always() }}
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
@ -156,8 +156,8 @@ jobs:
|
|||||||
uses: helm/kind-action@v1.12.0
|
uses: helm/kind-action@v1.12.0
|
||||||
- name: run integration
|
- name: run integration
|
||||||
run: |
|
run: |
|
||||||
poetry run coverage run manage.py test tests/integration
|
uv run coverage run manage.py test tests/integration
|
||||||
poetry run coverage xml
|
uv run coverage xml
|
||||||
- if: ${{ always() }}
|
- if: ${{ always() }}
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
@ -214,8 +214,8 @@ jobs:
|
|||||||
npm run build
|
npm run build
|
||||||
- name: run e2e
|
- name: run e2e
|
||||||
run: |
|
run: |
|
||||||
poetry run coverage run manage.py test ${{ matrix.job.glob }}
|
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
||||||
poetry run coverage xml
|
uv run coverage xml
|
||||||
- if: ${{ always() }}
|
- if: ${{ always() }}
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
|
@ -2,7 +2,7 @@ name: authentik-gen-update-webauthn-mds
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 1 1,15 * *'
|
- cron: "30 1 1,15 * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
POSTGRES_DB: authentik
|
POSTGRES_DB: authentik
|
||||||
@ -24,7 +24,7 @@ jobs:
|
|||||||
token: ${{ steps.generate_token.outputs.token }}
|
token: ${{ steps.generate_token.outputs.token }}
|
||||||
- name: Setup authentik env
|
- name: Setup authentik env
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
- run: poetry run ak update_webauthn_mds
|
- run: uv run ak update_webauthn_mds
|
||||||
- uses: peter-evans/create-pull-request@v7
|
- uses: peter-evans/create-pull-request@v7
|
||||||
id: cpr
|
id: cpr
|
||||||
with:
|
with:
|
||||||
|
4
.github/workflows/publish-source-docs.yml
vendored
4
.github/workflows/publish-source-docs.yml
vendored
@ -21,8 +21,8 @@ jobs:
|
|||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
- name: generate docs
|
- name: generate docs
|
||||||
run: |
|
run: |
|
||||||
poetry run make migrate
|
uv run make migrate
|
||||||
poetry run ak build_source_docs
|
uv run ak build_source_docs
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: netlify/actions/cli@master
|
uses: netlify/actions/cli@master
|
||||||
with:
|
with:
|
||||||
|
@ -36,10 +36,10 @@ jobs:
|
|||||||
run: make gen-client-ts
|
run: make gen-client-ts
|
||||||
- name: run extract
|
- name: run extract
|
||||||
run: |
|
run: |
|
||||||
poetry run make i18n-extract
|
uv run make i18n-extract
|
||||||
- name: run compile
|
- name: run compile
|
||||||
run: |
|
run: |
|
||||||
poetry run ak compilemessages
|
uv run ak compilemessages
|
||||||
make web-check-compile
|
make web-check-compile
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
46
.vscode/tasks.json
vendored
46
.vscode/tasks.json
vendored
@ -3,8 +3,13 @@
|
|||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "authentik/core: make",
|
"label": "authentik/core: make",
|
||||||
"command": "poetry",
|
"command": "uv",
|
||||||
"args": ["run", "make", "lint-fix", "lint"],
|
"args": [
|
||||||
|
"run",
|
||||||
|
"make",
|
||||||
|
"lint-fix",
|
||||||
|
"lint"
|
||||||
|
],
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"panel": "new"
|
"panel": "new"
|
||||||
},
|
},
|
||||||
@ -12,8 +17,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "authentik/core: run",
|
"label": "authentik/core: run",
|
||||||
"command": "poetry",
|
"command": "uv",
|
||||||
"args": ["run", "ak", "server"],
|
"args": [
|
||||||
|
"run",
|
||||||
|
"ak",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"panel": "dedicated",
|
"panel": "dedicated",
|
||||||
@ -23,13 +32,17 @@
|
|||||||
{
|
{
|
||||||
"label": "authentik/web: make",
|
"label": "authentik/web: make",
|
||||||
"command": "make",
|
"command": "make",
|
||||||
"args": ["web"],
|
"args": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
"group": "build"
|
"group": "build"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "authentik/web: watch",
|
"label": "authentik/web: watch",
|
||||||
"command": "make",
|
"command": "make",
|
||||||
"args": ["web-watch"],
|
"args": [
|
||||||
|
"web-watch"
|
||||||
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"panel": "dedicated",
|
"panel": "dedicated",
|
||||||
@ -39,19 +52,26 @@
|
|||||||
{
|
{
|
||||||
"label": "authentik: install",
|
"label": "authentik: install",
|
||||||
"command": "make",
|
"command": "make",
|
||||||
"args": ["install", "-j4"],
|
"args": [
|
||||||
|
"install",
|
||||||
|
"-j4"
|
||||||
|
],
|
||||||
"group": "build"
|
"group": "build"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "authentik/website: make",
|
"label": "authentik/website: make",
|
||||||
"command": "make",
|
"command": "make",
|
||||||
"args": ["website"],
|
"args": [
|
||||||
|
"website"
|
||||||
|
],
|
||||||
"group": "build"
|
"group": "build"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "authentik/website: watch",
|
"label": "authentik/website: watch",
|
||||||
"command": "make",
|
"command": "make",
|
||||||
"args": ["website-watch"],
|
"args": [
|
||||||
|
"website-watch"
|
||||||
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"panel": "dedicated",
|
"panel": "dedicated",
|
||||||
@ -60,8 +80,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "authentik/api: generate",
|
"label": "authentik/api: generate",
|
||||||
"command": "poetry",
|
"command": "uv",
|
||||||
"args": ["run", "make", "gen"],
|
"args": [
|
||||||
|
"run",
|
||||||
|
"make",
|
||||||
|
"gen"
|
||||||
|
],
|
||||||
"group": "build"
|
"group": "build"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,7 @@ schemas/ @goauthentik/backend
|
|||||||
scripts/ @goauthentik/backend
|
scripts/ @goauthentik/backend
|
||||||
tests/ @goauthentik/backend
|
tests/ @goauthentik/backend
|
||||||
pyproject.toml @goauthentik/backend
|
pyproject.toml @goauthentik/backend
|
||||||
poetry.lock @goauthentik/backend
|
uv.lock @goauthentik/backend
|
||||||
go.mod @goauthentik/backend
|
go.mod @goauthentik/backend
|
||||||
go.sum @goauthentik/backend
|
go.sum @goauthentik/backend
|
||||||
# Infrastructure
|
# Infrastructure
|
||||||
|
88
Dockerfile
88
Dockerfile
@ -3,8 +3,7 @@
|
|||||||
# Stage 1: Build website
|
# Stage 1: Build website
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
|
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production
|
||||||
GIT_UNAVAILABLE=true
|
|
||||||
|
|
||||||
WORKDIR /work/website
|
WORKDIR /work/website
|
||||||
|
|
||||||
@ -94,53 +93,59 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
|||||||
mkdir -p /usr/share/GeoIP && \
|
mkdir -p /usr/share/GeoIP && \
|
||||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Python dependencies
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS python-deps
|
FROM ghcr.io/astral-sh/uv:0.6.9 AS uv
|
||||||
|
# Stage 6: Base python image
|
||||||
|
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
|
ENV VENV_PATH="/ak-root/.venv" \
|
||||||
|
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||||
|
UV_COMPILE_BYTECODE=1 \
|
||||||
|
UV_LINK_MODE=copy \
|
||||||
|
UV_NATIVE_TLS=1 \
|
||||||
|
UV_PYTHON_DOWNLOADS=0
|
||||||
|
|
||||||
|
WORKDIR /ak-root/
|
||||||
|
|
||||||
|
COPY --from=uv /uv /uvx /bin/
|
||||||
|
|
||||||
|
# Stage 7: Python dependencies
|
||||||
|
FROM python-base AS python-deps
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
|
|
||||||
WORKDIR /ak-root/poetry
|
|
||||||
|
|
||||||
ENV VENV_PATH="/ak-root/venv" \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=false \
|
|
||||||
PATH="/ak-root/venv/bin:$PATH"
|
|
||||||
|
|
||||||
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||||
|
|
||||||
|
ENV PATH="/root/.cargo/bin:$PATH"
|
||||||
|
|
||||||
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
|
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
# Required for installing pip packages
|
# Required for installing pip packages
|
||||||
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev libkrb5-dev
|
|
||||||
|
|
||||||
RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
|
|
||||||
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
|
|
||||||
--mount=type=cache,target=/root/.cache/pip \
|
|
||||||
--mount=type=cache,target=/root/.cache/pypoetry \
|
|
||||||
pip install --no-cache cffi && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential libffi-dev \
|
# Build essentials
|
||||||
# Required for cryptography
|
build-essential pkg-config libffi-dev git \
|
||||||
curl pkg-config \
|
# cryptography
|
||||||
# Required for lxml
|
curl \
|
||||||
libxslt-dev zlib1g-dev \
|
# libxml
|
||||||
# Required for xmlsec
|
libxslt-dev zlib1g-dev \
|
||||||
libltdl-dev \
|
# postgresql
|
||||||
# Required for kadmin
|
libpq-dev \
|
||||||
sccache clang && \
|
# python-kadmin-rs
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
clang libkrb5-dev sccache \
|
||||||
. "$HOME/.cargo/env" && \
|
# xmlsec
|
||||||
python -m venv /ak-root/venv/ && \
|
libltdl-dev && \
|
||||||
bash -c "source ${VENV_PATH}/bin/activate && \
|
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
pip3 install --upgrade pip poetry && \
|
|
||||||
poetry config --local installer.no-binary cryptography,xmlsec,lxml,python-kadmin-rs && \
|
|
||||||
poetry install --only=main --no-ansi --no-interaction --no-root && \
|
|
||||||
pip uninstall cryptography -y && \
|
|
||||||
poetry install --only=main --no-ansi --no-interaction --no-root"
|
|
||||||
|
|
||||||
# Stage 6: Run
|
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec"
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS final-image
|
|
||||||
|
RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
|
||||||
|
--mount=type=bind,target=uv.lock,src=uv.lock \
|
||||||
|
--mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync --frozen --no-install-project --no-dev
|
||||||
|
|
||||||
|
# Stage 8: Run
|
||||||
|
FROM python-base AS final-image
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG GIT_BUILD_HASH
|
ARG GIT_BUILD_HASH
|
||||||
@ -172,7 +177,7 @@ RUN apt-get update && \
|
|||||||
|
|
||||||
COPY ./authentik/ /authentik
|
COPY ./authentik/ /authentik
|
||||||
COPY ./pyproject.toml /
|
COPY ./pyproject.toml /
|
||||||
COPY ./poetry.lock /
|
COPY ./uv.lock /
|
||||||
COPY ./schemas /schemas
|
COPY ./schemas /schemas
|
||||||
COPY ./locale /locale
|
COPY ./locale /locale
|
||||||
COPY ./tests /tests
|
COPY ./tests /tests
|
||||||
@ -181,7 +186,7 @@ COPY ./blueprints /blueprints
|
|||||||
COPY ./lifecycle/ /lifecycle
|
COPY ./lifecycle/ /lifecycle
|
||||||
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
|
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
|
||||||
COPY --from=go-builder /go/authentik /bin/authentik
|
COPY --from=go-builder /go/authentik /bin/authentik
|
||||||
COPY --from=python-deps /ak-root/venv /ak-root/venv
|
COPY --from=python-deps /ak-root/.venv /ak-root/.venv
|
||||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||||
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
||||||
COPY --from=website-builder /work/website/build/ /website/help/
|
COPY --from=website-builder /work/website/build/ /website/help/
|
||||||
@ -192,9 +197,6 @@ USER 1000
|
|||||||
ENV TMPDIR=/dev/shm/ \
|
ENV TMPDIR=/dev/shm/ \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
PATH="/ak-root/venv/bin:/lifecycle:$PATH" \
|
|
||||||
VENV_PATH="/ak-root/venv" \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=false \
|
|
||||||
GOFIPS=1
|
GOFIPS=1
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
||||||
|
51
Makefile
51
Makefile
@ -12,9 +12,9 @@ GEN_API_TS = "gen-ts-api"
|
|||||||
GEN_API_PY = "gen-py-api"
|
GEN_API_PY = "gen-py-api"
|
||||||
GEN_API_GO = "gen-go-api"
|
GEN_API_GO = "gen-go-api"
|
||||||
|
|
||||||
pg_user := $(shell poetry run python -m authentik.lib.config postgresql.user 2>/dev/null)
|
pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null)
|
||||||
pg_host := $(shell poetry run python -m authentik.lib.config postgresql.host 2>/dev/null)
|
pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null)
|
||||||
pg_name := $(shell poetry run python -m authentik.lib.config postgresql.name 2>/dev/null)
|
pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null)
|
||||||
|
|
||||||
all: lint-fix lint test gen web ## Lint, build, and test everything
|
all: lint-fix lint test gen web ## Lint, build, and test everything
|
||||||
|
|
||||||
@ -32,34 +32,37 @@ go-test:
|
|||||||
go test -timeout 0 -v -race -cover ./...
|
go test -timeout 0 -v -race -cover ./...
|
||||||
|
|
||||||
test: ## Run the server tests and produce a coverage report (locally)
|
test: ## Run the server tests and produce a coverage report (locally)
|
||||||
poetry run coverage run manage.py test --keepdb authentik
|
uv run coverage run manage.py test --keepdb authentik
|
||||||
poetry run coverage html
|
uv run coverage html
|
||||||
poetry run coverage report
|
uv run coverage report
|
||||||
|
|
||||||
lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
|
lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
|
||||||
poetry run black $(PY_SOURCES)
|
uv run black $(PY_SOURCES)
|
||||||
poetry run ruff check --fix $(PY_SOURCES)
|
uv run ruff check --fix $(PY_SOURCES)
|
||||||
|
|
||||||
lint-codespell: ## Reports spelling errors.
|
lint-codespell: ## Reports spelling errors.
|
||||||
poetry run codespell -w
|
uv run codespell -w
|
||||||
|
|
||||||
lint: ## Lint the python and golang sources
|
lint: ## Lint the python and golang sources
|
||||||
poetry run bandit -c pyproject.toml -r $(PY_SOURCES)
|
uv run bandit -c pyproject.toml -r $(PY_SOURCES)
|
||||||
golangci-lint run -v
|
golangci-lint run -v
|
||||||
|
|
||||||
core-install:
|
core-install:
|
||||||
poetry install
|
uv sync --frozen
|
||||||
|
|
||||||
migrate: ## Run the Authentik Django server's migrations
|
migrate: ## Run the Authentik Django server's migrations
|
||||||
poetry run python -m lifecycle.migrate
|
uv run python -m lifecycle.migrate
|
||||||
|
|
||||||
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
|
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
|
||||||
|
|
||||||
aws-cfn:
|
aws-cfn:
|
||||||
cd lifecycle/aws && npm run aws-cfn
|
cd lifecycle/aws && npm run aws-cfn
|
||||||
|
|
||||||
|
run: ## Run the main authentik server process
|
||||||
|
uv run ak server
|
||||||
|
|
||||||
core-i18n-extract:
|
core-i18n-extract:
|
||||||
poetry run ak makemessages \
|
uv run ak makemessages \
|
||||||
--add-location file \
|
--add-location file \
|
||||||
--no-obsolete \
|
--no-obsolete \
|
||||||
--ignore web \
|
--ignore web \
|
||||||
@ -90,11 +93,11 @@ gen-build: ## Extract the schema from the database
|
|||||||
AUTHENTIK_DEBUG=true \
|
AUTHENTIK_DEBUG=true \
|
||||||
AUTHENTIK_TENANTS__ENABLED=true \
|
AUTHENTIK_TENANTS__ENABLED=true \
|
||||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||||
poetry run ak make_blueprint_schema > blueprints/schema.json
|
uv run ak make_blueprint_schema > blueprints/schema.json
|
||||||
AUTHENTIK_DEBUG=true \
|
AUTHENTIK_DEBUG=true \
|
||||||
AUTHENTIK_TENANTS__ENABLED=true \
|
AUTHENTIK_TENANTS__ENABLED=true \
|
||||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||||
poetry run ak spectacular --file schema.yml
|
uv run ak spectacular --file schema.yml
|
||||||
|
|
||||||
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
|
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
|
||||||
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
|
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
|
||||||
@ -173,7 +176,7 @@ gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
|
|||||||
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
|
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
|
||||||
|
|
||||||
gen-dev-config: ## Generate a local development config file
|
gen-dev-config: ## Generate a local development config file
|
||||||
poetry run scripts/generate_config.py
|
uv run scripts/generate_config.py
|
||||||
|
|
||||||
gen: gen-build gen-client-ts
|
gen: gen-build gen-client-ts
|
||||||
|
|
||||||
@ -254,21 +257,21 @@ ci--meta-debug:
|
|||||||
node --version
|
node --version
|
||||||
|
|
||||||
ci-black: ci--meta-debug
|
ci-black: ci--meta-debug
|
||||||
poetry run black --check $(PY_SOURCES)
|
uv run black --check $(PY_SOURCES)
|
||||||
|
|
||||||
ci-ruff: ci--meta-debug
|
ci-ruff: ci--meta-debug
|
||||||
poetry run ruff check $(PY_SOURCES)
|
uv run ruff check $(PY_SOURCES)
|
||||||
|
|
||||||
ci-codespell: ci--meta-debug
|
ci-codespell: ci--meta-debug
|
||||||
poetry run codespell -s
|
uv run codespell -s
|
||||||
|
|
||||||
ci-bandit: ci--meta-debug
|
ci-bandit: ci--meta-debug
|
||||||
poetry run bandit -r $(PY_SOURCES)
|
uv run bandit -r $(PY_SOURCES)
|
||||||
|
|
||||||
ci-pending-migrations: ci--meta-debug
|
ci-pending-migrations: ci--meta-debug
|
||||||
poetry run ak makemigrations --check
|
uv run ak makemigrations --check
|
||||||
|
|
||||||
ci-test: ci--meta-debug
|
ci-test: ci--meta-debug
|
||||||
poetry run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
|
uv run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
|
||||||
poetry run coverage report
|
uv run coverage report
|
||||||
poetry run coverage xml
|
uv run coverage xml
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.2.1"
|
__version__ = "2025.2.2"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ class BrandSerializer(ModelSerializer):
|
|||||||
"branding_title",
|
"branding_title",
|
||||||
"branding_logo",
|
"branding_logo",
|
||||||
"branding_favicon",
|
"branding_favicon",
|
||||||
|
"branding_custom_css",
|
||||||
|
"branding_default_flow_background",
|
||||||
"flow_authentication",
|
"flow_authentication",
|
||||||
"flow_invalidation",
|
"flow_invalidation",
|
||||||
"flow_recovery",
|
"flow_recovery",
|
||||||
@ -86,6 +88,7 @@ class CurrentBrandSerializer(PassiveSerializer):
|
|||||||
branding_title = CharField()
|
branding_title = CharField()
|
||||||
branding_logo = CharField(source="branding_logo_url")
|
branding_logo = CharField(source="branding_logo_url")
|
||||||
branding_favicon = CharField(source="branding_favicon_url")
|
branding_favicon = CharField(source="branding_favicon_url")
|
||||||
|
branding_custom_css = CharField()
|
||||||
ui_footer_links = ListField(
|
ui_footer_links = ListField(
|
||||||
child=FooterLinkSerializer(),
|
child=FooterLinkSerializer(),
|
||||||
read_only=True,
|
read_only=True,
|
||||||
@ -125,6 +128,7 @@ class BrandViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"branding_title",
|
"branding_title",
|
||||||
"branding_logo",
|
"branding_logo",
|
||||||
"branding_favicon",
|
"branding_favicon",
|
||||||
|
"branding_default_flow_background",
|
||||||
"flow_authentication",
|
"flow_authentication",
|
||||||
"flow_invalidation",
|
"flow_invalidation",
|
||||||
"flow_recovery",
|
"flow_recovery",
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.0.12 on 2025-02-22 01:51
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_custom_css(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
Brand = apps.get_model("authentik_brands", "brand")
|
||||||
|
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
path = Path("/web/dist/custom.css")
|
||||||
|
if not path.exists():
|
||||||
|
return
|
||||||
|
with path.read_text() as css:
|
||||||
|
Brand.objects.using(db_alias).update(branding_custom_css=css)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_brands", "0007_brand_default_application"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="brand",
|
||||||
|
name="branding_custom_css",
|
||||||
|
field=models.TextField(blank=True, default=""),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_custom_css),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.13 on 2025-03-19 22:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_brands", "0008_brand_branding_custom_css"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="brand",
|
||||||
|
name="branding_default_flow_background",
|
||||||
|
field=models.TextField(default="/static/dist/assets/images/flow_background.jpg"),
|
||||||
|
),
|
||||||
|
]
|
@ -33,6 +33,10 @@ class Brand(SerializerModel):
|
|||||||
|
|
||||||
branding_logo = models.TextField(default="/static/dist/assets/icons/icon_left_brand.svg")
|
branding_logo = models.TextField(default="/static/dist/assets/icons/icon_left_brand.svg")
|
||||||
branding_favicon = models.TextField(default="/static/dist/assets/icons/icon.png")
|
branding_favicon = models.TextField(default="/static/dist/assets/icons/icon.png")
|
||||||
|
branding_custom_css = models.TextField(default="", blank=True)
|
||||||
|
branding_default_flow_background = models.TextField(
|
||||||
|
default="/static/dist/assets/images/flow_background.jpg"
|
||||||
|
)
|
||||||
|
|
||||||
flow_authentication = models.ForeignKey(
|
flow_authentication = models.ForeignKey(
|
||||||
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_authentication"
|
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_authentication"
|
||||||
@ -84,6 +88,12 @@ class Brand(SerializerModel):
|
|||||||
return CONFIG.get("web.path", "/")[:-1] + self.branding_favicon
|
return CONFIG.get("web.path", "/")[:-1] + self.branding_favicon
|
||||||
return self.branding_favicon
|
return self.branding_favicon
|
||||||
|
|
||||||
|
def branding_default_flow_background_url(self) -> str:
|
||||||
|
"""Get branding_default_flow_background with the correct prefix"""
|
||||||
|
if self.branding_default_flow_background.startswith("/static"):
|
||||||
|
return CONFIG.get("web.path", "/")[:-1] + self.branding_default_flow_background
|
||||||
|
return self.branding_default_flow_background
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> Serializer:
|
def serializer(self) -> Serializer:
|
||||||
from authentik.brands.api import BrandSerializer
|
from authentik.brands.api import BrandSerializer
|
||||||
|
@ -24,6 +24,7 @@ class TestBrands(APITestCase):
|
|||||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||||
"branding_title": "authentik",
|
"branding_title": "authentik",
|
||||||
|
"branding_custom_css": "",
|
||||||
"matched_domain": brand.domain,
|
"matched_domain": brand.domain,
|
||||||
"ui_footer_links": [],
|
"ui_footer_links": [],
|
||||||
"ui_theme": Themes.AUTOMATIC,
|
"ui_theme": Themes.AUTOMATIC,
|
||||||
@ -43,6 +44,7 @@ class TestBrands(APITestCase):
|
|||||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||||
"branding_title": "custom",
|
"branding_title": "custom",
|
||||||
|
"branding_custom_css": "",
|
||||||
"matched_domain": "bar.baz",
|
"matched_domain": "bar.baz",
|
||||||
"ui_footer_links": [],
|
"ui_footer_links": [],
|
||||||
"ui_theme": Themes.AUTOMATIC,
|
"ui_theme": Themes.AUTOMATIC,
|
||||||
@ -59,6 +61,7 @@ class TestBrands(APITestCase):
|
|||||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||||
"branding_title": "authentik",
|
"branding_title": "authentik",
|
||||||
|
"branding_custom_css": "",
|
||||||
"matched_domain": "fallback",
|
"matched_domain": "fallback",
|
||||||
"ui_footer_links": [],
|
"ui_footer_links": [],
|
||||||
"ui_theme": Themes.AUTOMATIC,
|
"ui_theme": Themes.AUTOMATIC,
|
||||||
@ -121,3 +124,27 @@ class TestBrands(APITestCase):
|
|||||||
"subject": None,
|
"subject": None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_branding_url(self):
|
||||||
|
"""Test branding attributes return correct values"""
|
||||||
|
brand = create_test_brand()
|
||||||
|
brand.branding_default_flow_background = "https://goauthentik.io/img/icon.png"
|
||||||
|
brand.branding_favicon = "https://goauthentik.io/img/icon.png"
|
||||||
|
brand.branding_logo = "https://goauthentik.io/img/icon.png"
|
||||||
|
brand.save()
|
||||||
|
self.assertEqual(
|
||||||
|
brand.branding_default_flow_background_url(), "https://goauthentik.io/img/icon.png"
|
||||||
|
)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
self.client.get(reverse("authentik_api:brand-current")).content.decode(),
|
||||||
|
{
|
||||||
|
"branding_logo": "https://goauthentik.io/img/icon.png",
|
||||||
|
"branding_favicon": "https://goauthentik.io/img/icon.png",
|
||||||
|
"branding_title": "authentik",
|
||||||
|
"branding_custom_css": "",
|
||||||
|
"matched_domain": brand.domain,
|
||||||
|
"ui_footer_links": [],
|
||||||
|
"ui_theme": Themes.AUTOMATIC,
|
||||||
|
"default_locale": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
{% block head_before %}
|
{% block head_before %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
|
<style>{{ brand.branding_custom_css }}</style>
|
||||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block head_before %}
|
{% block head_before %}
|
||||||
<link rel="prefetch" href="{% static 'dist/assets/images/flow_background.jpg' %}" />
|
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
|
||||||
{% include "base/header_js.html" %}
|
{% include "base/header_js.html" %}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ak-flow-background: url("{% static 'dist/assets/images/flow_background.jpg' %}");
|
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
|
||||||
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
|
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
|
||||||
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
|
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
|
||||||
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
|
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
|
||||||
|
@ -50,7 +50,8 @@ class NotificationTransportSerializer(ModelSerializer):
|
|||||||
"mode",
|
"mode",
|
||||||
"mode_verbose",
|
"mode_verbose",
|
||||||
"webhook_url",
|
"webhook_url",
|
||||||
"webhook_mapping",
|
"webhook_mapping_body",
|
||||||
|
"webhook_mapping_headers",
|
||||||
"send_once",
|
"send_once",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 5.0.13 on 2025-03-20 19:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_events", "0008_event_authentik_e_expires_8c73a8_idx_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="notificationtransport",
|
||||||
|
old_name="webhook_mapping",
|
||||||
|
new_name="webhook_mapping_body",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notificationtransport",
|
||||||
|
name="webhook_mapping_body",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
default=None,
|
||||||
|
help_text="Customize the body of the request. Mapping should return data that is JSON-serializable.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||||
|
related_name="+",
|
||||||
|
to="authentik_events.notificationwebhookmapping",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="notificationtransport",
|
||||||
|
name="webhook_mapping_headers",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
default=None,
|
||||||
|
help_text="Configure additional headers to be sent. Mapping should return a dictionary of key-value pairs",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||||
|
related_name="+",
|
||||||
|
to="authentik_events.notificationwebhookmapping",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -336,8 +336,27 @@ class NotificationTransport(SerializerModel):
|
|||||||
mode = models.TextField(choices=TransportMode.choices, default=TransportMode.LOCAL)
|
mode = models.TextField(choices=TransportMode.choices, default=TransportMode.LOCAL)
|
||||||
|
|
||||||
webhook_url = models.TextField(blank=True, validators=[DomainlessURLValidator()])
|
webhook_url = models.TextField(blank=True, validators=[DomainlessURLValidator()])
|
||||||
webhook_mapping = models.ForeignKey(
|
webhook_mapping_body = models.ForeignKey(
|
||||||
"NotificationWebhookMapping", on_delete=models.SET_DEFAULT, null=True, default=None
|
"NotificationWebhookMapping",
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
related_name="+",
|
||||||
|
help_text=_(
|
||||||
|
"Customize the body of the request. "
|
||||||
|
"Mapping should return data that is JSON-serializable."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
webhook_mapping_headers = models.ForeignKey(
|
||||||
|
"NotificationWebhookMapping",
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
related_name="+",
|
||||||
|
help_text=_(
|
||||||
|
"Configure additional headers to be sent. "
|
||||||
|
"Mapping should return a dictionary of key-value pairs"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
send_once = models.BooleanField(
|
send_once = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
@ -360,8 +379,8 @@ class NotificationTransport(SerializerModel):
|
|||||||
|
|
||||||
def send_local(self, notification: "Notification") -> list[str]:
|
def send_local(self, notification: "Notification") -> list[str]:
|
||||||
"""Local notification delivery"""
|
"""Local notification delivery"""
|
||||||
if self.webhook_mapping:
|
if self.webhook_mapping_body:
|
||||||
self.webhook_mapping.evaluate(
|
self.webhook_mapping_body.evaluate(
|
||||||
user=notification.user,
|
user=notification.user,
|
||||||
request=None,
|
request=None,
|
||||||
notification=notification,
|
notification=notification,
|
||||||
@ -380,9 +399,18 @@ class NotificationTransport(SerializerModel):
|
|||||||
if notification.event and notification.event.user:
|
if notification.event and notification.event.user:
|
||||||
default_body["event_user_email"] = notification.event.user.get("email", None)
|
default_body["event_user_email"] = notification.event.user.get("email", None)
|
||||||
default_body["event_user_username"] = notification.event.user.get("username", None)
|
default_body["event_user_username"] = notification.event.user.get("username", None)
|
||||||
if self.webhook_mapping:
|
headers = {}
|
||||||
|
if self.webhook_mapping_body:
|
||||||
default_body = sanitize_item(
|
default_body = sanitize_item(
|
||||||
self.webhook_mapping.evaluate(
|
self.webhook_mapping_body.evaluate(
|
||||||
|
user=notification.user,
|
||||||
|
request=None,
|
||||||
|
notification=notification,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.webhook_mapping_headers:
|
||||||
|
headers = sanitize_item(
|
||||||
|
self.webhook_mapping_headers.evaluate(
|
||||||
user=notification.user,
|
user=notification.user,
|
||||||
request=None,
|
request=None,
|
||||||
notification=notification,
|
notification=notification,
|
||||||
@ -392,6 +420,7 @@ class NotificationTransport(SerializerModel):
|
|||||||
response = get_http_session().post(
|
response = get_http_session().post(
|
||||||
self.webhook_url,
|
self.webhook_url,
|
||||||
json=default_body,
|
json=default_body,
|
||||||
|
headers=headers,
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except RequestException as exc:
|
except RequestException as exc:
|
||||||
|
@ -120,7 +120,7 @@ class TestEventsNotifications(APITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
transport = NotificationTransport.objects.create(
|
transport = NotificationTransport.objects.create(
|
||||||
name=generate_id(), webhook_mapping=mapping, mode=TransportMode.LOCAL
|
name=generate_id(), webhook_mapping_body=mapping, mode=TransportMode.LOCAL
|
||||||
)
|
)
|
||||||
NotificationRule.objects.filter(name__startswith="default").delete()
|
NotificationRule.objects.filter(name__startswith="default").delete()
|
||||||
trigger = NotificationRule.objects.create(name=generate_id(), group=self.group)
|
trigger = NotificationRule.objects.create(name=generate_id(), group=self.group)
|
||||||
|
@ -60,20 +60,25 @@ class TestEventTransports(TestCase):
|
|||||||
|
|
||||||
def test_transport_webhook_mapping(self):
|
def test_transport_webhook_mapping(self):
|
||||||
"""Test webhook transport with custom mapping"""
|
"""Test webhook transport with custom mapping"""
|
||||||
mapping = NotificationWebhookMapping.objects.create(
|
mapping_body = NotificationWebhookMapping.objects.create(
|
||||||
name=generate_id(), expression="return request.user"
|
name=generate_id(), expression="return request.user"
|
||||||
)
|
)
|
||||||
|
mapping_headers = NotificationWebhookMapping.objects.create(
|
||||||
|
name=generate_id(), expression="""return {"foo": "bar"}"""
|
||||||
|
)
|
||||||
transport: NotificationTransport = NotificationTransport.objects.create(
|
transport: NotificationTransport = NotificationTransport.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
mode=TransportMode.WEBHOOK,
|
mode=TransportMode.WEBHOOK,
|
||||||
webhook_url="http://localhost:1234/test",
|
webhook_url="http://localhost:1234/test",
|
||||||
webhook_mapping=mapping,
|
webhook_mapping_body=mapping_body,
|
||||||
|
webhook_mapping_headers=mapping_headers,
|
||||||
)
|
)
|
||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
mocker.post("http://localhost:1234/test")
|
mocker.post("http://localhost:1234/test")
|
||||||
transport.send(self.notification)
|
transport.send(self.notification)
|
||||||
self.assertEqual(mocker.call_count, 1)
|
self.assertEqual(mocker.call_count, 1)
|
||||||
self.assertEqual(mocker.request_history[0].method, "POST")
|
self.assertEqual(mocker.request_history[0].method, "POST")
|
||||||
|
self.assertEqual(mocker.request_history[0].headers["foo"], "bar")
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
mocker.request_history[0].body.decode(),
|
mocker.request_history[0].body.decode(),
|
||||||
{"email": self.user.email, "pk": self.user.pk, "username": self.user.username},
|
{"email": self.user.email, "pk": self.user.pk, "username": self.user.username},
|
||||||
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
from rest_framework.serializers import BaseSerializer
|
from rest_framework.serializers import BaseSerializer
|
||||||
@ -178,14 +179,11 @@ class Flow(SerializerModel, PolicyBindingModel):
|
|||||||
help_text=_("Required level of authentication and authorization to access a flow."),
|
help_text=_("Required level of authentication and authorization to access a flow."),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
def background_url(self, request: HttpRequest) -> str:
|
||||||
def background_url(self) -> str:
|
|
||||||
"""Get the URL to the background image. If the name is /static or starts with http
|
"""Get the URL to the background image. If the name is /static or starts with http
|
||||||
it is returned as-is"""
|
it is returned as-is"""
|
||||||
if not self.background:
|
if not self.background:
|
||||||
return (
|
return request.brand.branding_default_flow_background_url()
|
||||||
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg"
|
|
||||||
)
|
|
||||||
if self.background.name.startswith("http"):
|
if self.background.name.startswith("http"):
|
||||||
return self.background.name
|
return self.background.name
|
||||||
if self.background.name.startswith("/static"):
|
if self.background.name.startswith("/static"):
|
||||||
|
@ -184,7 +184,7 @@ class ChallengeStageView(StageView):
|
|||||||
flow_info = ContextualFlowInfo(
|
flow_info = ContextualFlowInfo(
|
||||||
data={
|
data={
|
||||||
"title": self.format_title(),
|
"title": self.format_title(),
|
||||||
"background": self.executor.flow.background_url,
|
"background": self.executor.flow.background_url(self.request),
|
||||||
"cancel_url": reverse("authentik_flows:cancel"),
|
"cancel_url": reverse("authentik_flows:cancel"),
|
||||||
"layout": self.executor.flow.layout,
|
"layout": self.executor.flow.layout,
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ class FlowTestCase(APITestCase):
|
|||||||
self.assertIsNotNone(raw_response["component"])
|
self.assertIsNotNone(raw_response["component"])
|
||||||
if flow:
|
if flow:
|
||||||
self.assertIn("flow_info", raw_response)
|
self.assertIn("flow_info", raw_response)
|
||||||
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
|
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,7 @@ class TestFlowInspector(APITestCase):
|
|||||||
"captcha_stage": None,
|
"captcha_stage": None,
|
||||||
"component": "ak-stage-identification",
|
"component": "ak-stage-identification",
|
||||||
"flow_info": {
|
"flow_info": {
|
||||||
"background": flow.background_url,
|
"background": "/static/dist/assets/images/flow_background.jpg",
|
||||||
"cancel_url": reverse("authentik_flows:cancel"),
|
"cancel_url": reverse("authentik_flows:cancel"),
|
||||||
"title": flow.title,
|
"title": flow.title,
|
||||||
"layout": "stacked",
|
"layout": "stacked",
|
||||||
|
@ -45,6 +45,8 @@ redis:
|
|||||||
# url: ""
|
# url: ""
|
||||||
# transport_options: ""
|
# transport_options: ""
|
||||||
|
|
||||||
|
http_timeout: 30
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
# url: ""
|
# url: ""
|
||||||
timeout: 300
|
timeout: 300
|
||||||
|
@ -16,7 +16,40 @@ def authentik_user_agent() -> str:
|
|||||||
return f"authentik@{get_full_version()}"
|
return f"authentik@{get_full_version()}"
|
||||||
|
|
||||||
|
|
||||||
class DebugSession(Session):
|
class TimeoutSession(Session):
|
||||||
|
"""Always set a default HTTP request timeout"""
|
||||||
|
|
||||||
|
def __init__(self, default_timeout=None):
|
||||||
|
super().__init__()
|
||||||
|
self.timeout = default_timeout
|
||||||
|
|
||||||
|
def send(
|
||||||
|
self,
|
||||||
|
request,
|
||||||
|
*,
|
||||||
|
stream=...,
|
||||||
|
verify=...,
|
||||||
|
proxies=...,
|
||||||
|
cert=...,
|
||||||
|
timeout=...,
|
||||||
|
allow_redirects=...,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
if not timeout and self.timeout:
|
||||||
|
timeout = self.timeout
|
||||||
|
return super().send(
|
||||||
|
request,
|
||||||
|
stream=stream,
|
||||||
|
verify=verify,
|
||||||
|
proxies=proxies,
|
||||||
|
cert=cert,
|
||||||
|
timeout=timeout,
|
||||||
|
allow_redirects=allow_redirects,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DebugSession(TimeoutSession):
|
||||||
"""requests session which logs http requests and responses"""
|
"""requests session which logs http requests and responses"""
|
||||||
|
|
||||||
def send(self, req: PreparedRequest, *args, **kwargs):
|
def send(self, req: PreparedRequest, *args, **kwargs):
|
||||||
@ -42,8 +75,9 @@ class DebugSession(Session):
|
|||||||
|
|
||||||
def get_http_session() -> Session:
|
def get_http_session() -> Session:
|
||||||
"""Get a requests session with common headers"""
|
"""Get a requests session with common headers"""
|
||||||
session = Session()
|
session = TimeoutSession()
|
||||||
if CONFIG.get_bool("debug") or CONFIG.get("log_level") == "trace":
|
if CONFIG.get_bool("debug") or CONFIG.get("log_level") == "trace":
|
||||||
session = DebugSession()
|
session = DebugSession()
|
||||||
session.headers["User-Agent"] = authentik_user_agent()
|
session.headers["User-Agent"] = authentik_user_agent()
|
||||||
|
session.timeout = CONFIG.get_optional_int("http_timeout")
|
||||||
return session
|
return session
|
||||||
|
@ -180,6 +180,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
|||||||
"session_valid_not_on_or_after",
|
"session_valid_not_on_or_after",
|
||||||
"property_mappings",
|
"property_mappings",
|
||||||
"name_id_mapping",
|
"name_id_mapping",
|
||||||
|
"authn_context_class_ref_mapping",
|
||||||
"digest_algorithm",
|
"digest_algorithm",
|
||||||
"signature_algorithm",
|
"signature_algorithm",
|
||||||
"signing_kp",
|
"signing_kp",
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.13 on 2025-03-18 17:41
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_providers_saml", "0016_samlprovider_encryption_kp_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="samlprovider",
|
||||||
|
name="authn_context_class_ref_mapping",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Configure how the AuthnContextClassRef value will be created. When left empty, the AuthnContextClassRef will be set based on which authentication methods the user used to authenticate.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||||
|
related_name="+",
|
||||||
|
to="authentik_providers_saml.samlpropertymapping",
|
||||||
|
verbose_name="AuthnContextClassRef Property Mapping",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -71,6 +71,20 @@ class SAMLProvider(Provider):
|
|||||||
"the NameIDPolicy of the incoming request will be considered"
|
"the NameIDPolicy of the incoming request will be considered"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
authn_context_class_ref_mapping = models.ForeignKey(
|
||||||
|
"SAMLPropertyMapping",
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
verbose_name=_("AuthnContextClassRef Property Mapping"),
|
||||||
|
related_name="+",
|
||||||
|
help_text=_(
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left empty, "
|
||||||
|
"the AuthnContextClassRef will be set based on which authentication methods the user "
|
||||||
|
"used to authenticate."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
assertion_valid_not_before = models.TextField(
|
assertion_valid_not_before = models.TextField(
|
||||||
default="minutes=-5",
|
default="minutes=-5",
|
||||||
@ -170,7 +184,6 @@ class SAMLProvider(Provider):
|
|||||||
def launch_url(self) -> str | None:
|
def launch_url(self) -> str | None:
|
||||||
"""Use IDP-Initiated SAML flow as launch URL"""
|
"""Use IDP-Initiated SAML flow as launch URL"""
|
||||||
try:
|
try:
|
||||||
|
|
||||||
return reverse(
|
return reverse(
|
||||||
"authentik_providers_saml:sso-init",
|
"authentik_providers_saml:sso-init",
|
||||||
kwargs={"application_slug": self.application.slug},
|
kwargs={"application_slug": self.application.slug},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""SAML Assertion generator"""
|
"""SAML Assertion generator"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from types import GeneratorType
|
from types import GeneratorType
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ class AssertionProcessor:
|
|||||||
_assertion_id: str
|
_assertion_id: str
|
||||||
_response_id: str
|
_response_id: str
|
||||||
|
|
||||||
|
_auth_instant: str
|
||||||
_valid_not_before: str
|
_valid_not_before: str
|
||||||
_session_not_on_or_after: str
|
_session_not_on_or_after: str
|
||||||
_valid_not_on_or_after: str
|
_valid_not_on_or_after: str
|
||||||
@ -65,6 +67,11 @@ class AssertionProcessor:
|
|||||||
self._assertion_id = get_random_id()
|
self._assertion_id = get_random_id()
|
||||||
self._response_id = get_random_id()
|
self._response_id = get_random_id()
|
||||||
|
|
||||||
|
_login_event = get_login_event(self.http_request)
|
||||||
|
_login_time = datetime.now()
|
||||||
|
if _login_event:
|
||||||
|
_login_time = _login_event.created
|
||||||
|
self._auth_instant = get_time_string(_login_time)
|
||||||
self._valid_not_before = get_time_string(
|
self._valid_not_before = get_time_string(
|
||||||
timedelta_from_string(self.provider.assertion_valid_not_before)
|
timedelta_from_string(self.provider.assertion_valid_not_before)
|
||||||
)
|
)
|
||||||
@ -131,7 +138,7 @@ class AssertionProcessor:
|
|||||||
def get_assertion_auth_n_statement(self) -> Element:
|
def get_assertion_auth_n_statement(self) -> Element:
|
||||||
"""Generate AuthnStatement with AuthnContext and ContextClassRef Elements."""
|
"""Generate AuthnStatement with AuthnContext and ContextClassRef Elements."""
|
||||||
auth_n_statement = Element(f"{{{NS_SAML_ASSERTION}}}AuthnStatement")
|
auth_n_statement = Element(f"{{{NS_SAML_ASSERTION}}}AuthnStatement")
|
||||||
auth_n_statement.attrib["AuthnInstant"] = self._valid_not_before
|
auth_n_statement.attrib["AuthnInstant"] = self._auth_instant
|
||||||
auth_n_statement.attrib["SessionIndex"] = sha256(
|
auth_n_statement.attrib["SessionIndex"] = sha256(
|
||||||
self.http_request.session.session_key.encode("ascii")
|
self.http_request.session.session_key.encode("ascii")
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
@ -158,6 +165,28 @@ class AssertionProcessor:
|
|||||||
auth_n_context_class_ref.text = (
|
auth_n_context_class_ref.text = (
|
||||||
"urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract"
|
"urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract"
|
||||||
)
|
)
|
||||||
|
if self.provider.authn_context_class_ref_mapping:
|
||||||
|
try:
|
||||||
|
value = self.provider.authn_context_class_ref_mapping.evaluate(
|
||||||
|
user=self.http_request.user,
|
||||||
|
request=self.http_request,
|
||||||
|
provider=self.provider,
|
||||||
|
)
|
||||||
|
if value is not None:
|
||||||
|
auth_n_context_class_ref.text = str(value)
|
||||||
|
return auth_n_statement
|
||||||
|
except PropertyMappingExpressionException as exc:
|
||||||
|
Event.new(
|
||||||
|
EventAction.CONFIGURATION_ERROR,
|
||||||
|
message=(
|
||||||
|
"Failed to evaluate property-mapping: "
|
||||||
|
f"'{self.provider.authn_context_class_ref_mapping.name}'"
|
||||||
|
),
|
||||||
|
provider=self.provider,
|
||||||
|
mapping=self.provider.authn_context_class_ref_mapping,
|
||||||
|
).from_http(self.http_request)
|
||||||
|
LOGGER.warning("Failed to evaluate property mapping", exc=exc)
|
||||||
|
return auth_n_statement
|
||||||
return auth_n_statement
|
return auth_n_statement
|
||||||
|
|
||||||
def get_assertion_conditions(self) -> Element:
|
def get_assertion_conditions(self) -> Element:
|
||||||
|
@ -294,6 +294,61 @@ class TestAuthNRequest(TestCase):
|
|||||||
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
|
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
|
||||||
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
|
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
|
||||||
|
|
||||||
|
def test_authn_context_class_ref_mapping(self):
|
||||||
|
"""Test custom authn_context_class_ref"""
|
||||||
|
authn_context_class_ref = generate_id()
|
||||||
|
mapping = SAMLPropertyMapping.objects.create(
|
||||||
|
name=generate_id(), expression=f"""return '{authn_context_class_ref}'"""
|
||||||
|
)
|
||||||
|
self.provider.authn_context_class_ref_mapping = mapping
|
||||||
|
self.provider.save()
|
||||||
|
user = create_test_admin_user()
|
||||||
|
http_request = get_request("/", user=user)
|
||||||
|
|
||||||
|
# First create an AuthNRequest
|
||||||
|
request_proc = RequestProcessor(self.source, http_request, "test_state")
|
||||||
|
request = request_proc.build_auth_n()
|
||||||
|
|
||||||
|
# To get an assertion we need a parsed request (parsed by provider)
|
||||||
|
parsed_request = AuthNRequestParser(self.provider).parse(
|
||||||
|
b64encode(request.encode()).decode(), "test_state"
|
||||||
|
)
|
||||||
|
# Now create a response and convert it to string (provider)
|
||||||
|
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
|
||||||
|
response = response_proc.build_response()
|
||||||
|
self.assertIn(user.username, response)
|
||||||
|
self.assertIn(authn_context_class_ref, response)
|
||||||
|
|
||||||
|
def test_authn_context_class_ref_mapping_invalid(self):
|
||||||
|
"""Test custom authn_context_class_ref (invalid)"""
|
||||||
|
mapping = SAMLPropertyMapping.objects.create(name=generate_id(), expression="q")
|
||||||
|
self.provider.authn_context_class_ref_mapping = mapping
|
||||||
|
self.provider.save()
|
||||||
|
user = create_test_admin_user()
|
||||||
|
http_request = get_request("/", user=user)
|
||||||
|
|
||||||
|
# First create an AuthNRequest
|
||||||
|
request_proc = RequestProcessor(self.source, http_request, "test_state")
|
||||||
|
request = request_proc.build_auth_n()
|
||||||
|
|
||||||
|
# To get an assertion we need a parsed request (parsed by provider)
|
||||||
|
parsed_request = AuthNRequestParser(self.provider).parse(
|
||||||
|
b64encode(request.encode()).decode(), "test_state"
|
||||||
|
)
|
||||||
|
# Now create a response and convert it to string (provider)
|
||||||
|
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
|
||||||
|
response = response_proc.build_response()
|
||||||
|
self.assertIn(user.username, response)
|
||||||
|
|
||||||
|
events = Event.objects.filter(
|
||||||
|
action=EventAction.CONFIGURATION_ERROR,
|
||||||
|
)
|
||||||
|
self.assertTrue(events.exists())
|
||||||
|
self.assertEqual(
|
||||||
|
events.first().context["message"],
|
||||||
|
f"Failed to evaluate property-mapping: '{mapping.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
def test_request_attributes(self):
|
def test_request_attributes(self):
|
||||||
"""Test full SAML Request/Response flow, fully signed"""
|
"""Test full SAML Request/Response flow, fully signed"""
|
||||||
user = create_test_admin_user()
|
user = create_test_admin_user()
|
||||||
@ -321,8 +376,10 @@ class TestAuthNRequest(TestCase):
|
|||||||
request = request_proc.build_auth_n()
|
request = request_proc.build_auth_n()
|
||||||
|
|
||||||
# Create invalid PropertyMapping
|
# Create invalid PropertyMapping
|
||||||
scope = SAMLPropertyMapping.objects.create(name="test", saml_name="test", expression="q")
|
mapping = SAMLPropertyMapping.objects.create(
|
||||||
self.provider.property_mappings.add(scope)
|
name=generate_id(), saml_name="test", expression="q"
|
||||||
|
)
|
||||||
|
self.provider.property_mappings.add(mapping)
|
||||||
|
|
||||||
# To get an assertion we need a parsed request (parsed by provider)
|
# To get an assertion we need a parsed request (parsed by provider)
|
||||||
parsed_request = AuthNRequestParser(self.provider).parse(
|
parsed_request = AuthNRequestParser(self.provider).parse(
|
||||||
@ -338,7 +395,7 @@ class TestAuthNRequest(TestCase):
|
|||||||
self.assertTrue(events.exists())
|
self.assertTrue(events.exists())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
events.first().context["message"],
|
events.first().context["message"],
|
||||||
"Failed to evaluate property-mapping: 'test'",
|
f"Failed to evaluate property-mapping: '{mapping.name}'",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_idp_initiated(self):
|
def test_idp_initiated(self):
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
"""Time utilities"""
|
"""Time utilities"""
|
||||||
|
|
||||||
import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
|
||||||
def get_time_string(delta: datetime.timedelta | None = None) -> str:
|
def get_time_string(delta: timedelta | datetime | None = None) -> str:
|
||||||
"""Get Data formatted in SAML format"""
|
"""Get Data formatted in SAML format"""
|
||||||
if delta is None:
|
if delta is None:
|
||||||
delta = datetime.timedelta()
|
delta = timedelta()
|
||||||
now = datetime.datetime.now()
|
if isinstance(delta, timedelta):
|
||||||
final = now + delta
|
final = now() + delta
|
||||||
|
else:
|
||||||
|
final = delta
|
||||||
return final.strftime("%Y-%m-%dT%H:%M:%SZ")
|
return final.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
@ -24,7 +24,9 @@ class SCIMProviderGroupSerializer(ModelSerializer):
|
|||||||
"group",
|
"group",
|
||||||
"group_obj",
|
"group_obj",
|
||||||
"provider",
|
"provider",
|
||||||
|
"attributes",
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {"attributes": {"read_only": True}}
|
||||||
|
|
||||||
|
|
||||||
class SCIMProviderGroupViewSet(
|
class SCIMProviderGroupViewSet(
|
||||||
|
@ -24,7 +24,9 @@ class SCIMProviderUserSerializer(ModelSerializer):
|
|||||||
"user",
|
"user",
|
||||||
"user_obj",
|
"user_obj",
|
||||||
"provider",
|
"provider",
|
||||||
|
"attributes",
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {"attributes": {"read_only": True}}
|
||||||
|
|
||||||
|
|
||||||
class SCIMProviderUserViewSet(
|
class SCIMProviderUserViewSet(
|
||||||
|
@ -102,7 +102,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
if not scim_id or scim_id == "":
|
if not scim_id or scim_id == "":
|
||||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||||
connection = SCIMProviderGroup.objects.create(
|
connection = SCIMProviderGroup.objects.create(
|
||||||
provider=self.provider, group=group, scim_id=scim_id
|
provider=self.provider, group=group, scim_id=scim_id, attributes=response
|
||||||
)
|
)
|
||||||
users = list(group.users.order_by("id").values_list("id", flat=True))
|
users = list(group.users.order_by("id").values_list("id", flat=True))
|
||||||
self._patch_add_users(connection, users)
|
self._patch_add_users(connection, users)
|
||||||
|
@ -77,21 +77,24 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
|||||||
if len(users_res) < 1:
|
if len(users_res) < 1:
|
||||||
raise exc
|
raise exc
|
||||||
return SCIMProviderUser.objects.create(
|
return SCIMProviderUser.objects.create(
|
||||||
provider=self.provider, user=user, scim_id=users_res[0]["id"]
|
provider=self.provider,
|
||||||
|
user=user,
|
||||||
|
scim_id=users_res[0]["id"],
|
||||||
|
attributes=users_res[0],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
scim_id = response.get("id")
|
scim_id = response.get("id")
|
||||||
if not scim_id or scim_id == "":
|
if not scim_id or scim_id == "":
|
||||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||||
return SCIMProviderUser.objects.create(
|
return SCIMProviderUser.objects.create(
|
||||||
provider=self.provider, user=user, scim_id=scim_id
|
provider=self.provider, user=user, scim_id=scim_id, attributes=response
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(self, user: User, connection: SCIMProviderUser):
|
def update(self, user: User, connection: SCIMProviderUser):
|
||||||
"""Update existing user"""
|
"""Update existing user"""
|
||||||
scim_user = self.to_schema(user, connection)
|
scim_user = self.to_schema(user, connection)
|
||||||
scim_user.id = connection.scim_id
|
scim_user.id = connection.scim_id
|
||||||
self._request(
|
response = self._request(
|
||||||
"PUT",
|
"PUT",
|
||||||
f"/Users/{connection.scim_id}",
|
f"/Users/{connection.scim_id}",
|
||||||
json=scim_user.model_dump(
|
json=scim_user.model_dump(
|
||||||
@ -99,3 +102,5 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
|||||||
exclude_unset=True,
|
exclude_unset=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
connection.attributes = response
|
||||||
|
connection.save()
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0.13 on 2025-03-18 13:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_providers_scim", "0012_scimprovider_compatibility_mode"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scimprovidergroup",
|
||||||
|
name="attributes",
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scimprovideruser",
|
||||||
|
name="attributes",
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
]
|
@ -22,6 +22,7 @@ class SCIMProviderUser(SerializerModel):
|
|||||||
scim_id = models.TextField()
|
scim_id = models.TextField()
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
||||||
|
attributes = models.JSONField(default=dict)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
@ -43,6 +44,7 @@ class SCIMProviderGroup(SerializerModel):
|
|||||||
scim_id = models.TextField()
|
scim_id = models.TextField()
|
||||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
||||||
|
attributes = models.JSONField(default=dict)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
|
File diff suppressed because one or more lines are too long
@ -142,38 +142,35 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||||||
raise ValidationError("Failed to authenticate.")
|
raise ValidationError("Failed to authenticate.")
|
||||||
self.pre_user = pre_user
|
self.pre_user = pre_user
|
||||||
|
|
||||||
|
# Password check
|
||||||
|
if current_stage.password_stage:
|
||||||
|
password = attrs.get("password", None)
|
||||||
|
if not password:
|
||||||
|
self.stage.logger.warning("Password not set for ident+auth attempt")
|
||||||
|
try:
|
||||||
|
with start_span(
|
||||||
|
op="authentik.stages.identification.authenticate",
|
||||||
|
name="User authenticate call (combo stage)",
|
||||||
|
):
|
||||||
|
user = authenticate(
|
||||||
|
self.stage.request,
|
||||||
|
current_stage.password_stage.backends,
|
||||||
|
current_stage,
|
||||||
|
username=self.pre_user.username,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
if not user:
|
||||||
|
raise ValidationError("Failed to authenticate.")
|
||||||
|
self.pre_user = user
|
||||||
|
except PermissionDenied as exc:
|
||||||
|
raise ValidationError(str(exc)) from exc
|
||||||
|
|
||||||
# Captcha check
|
# Captcha check
|
||||||
if captcha_stage := current_stage.captcha_stage:
|
if captcha_stage := current_stage.captcha_stage:
|
||||||
captcha_token = attrs.get("captcha_token", None)
|
captcha_token = attrs.get("captcha_token", None)
|
||||||
if not captcha_token:
|
if not captcha_token:
|
||||||
self.stage.logger.warning("Token not set for captcha attempt")
|
self.stage.logger.warning("Token not set for captcha attempt")
|
||||||
verify_captcha_token(captcha_stage, captcha_token, client_ip)
|
verify_captcha_token(captcha_stage, captcha_token, client_ip)
|
||||||
|
|
||||||
# Password check
|
|
||||||
if not current_stage.password_stage:
|
|
||||||
# No password stage select, don't validate the password
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
password = attrs.get("password", None)
|
|
||||||
if not password:
|
|
||||||
self.stage.logger.warning("Password not set for ident+auth attempt")
|
|
||||||
try:
|
|
||||||
with start_span(
|
|
||||||
op="authentik.stages.identification.authenticate",
|
|
||||||
name="User authenticate call (combo stage)",
|
|
||||||
):
|
|
||||||
user = authenticate(
|
|
||||||
self.stage.request,
|
|
||||||
current_stage.password_stage.backends,
|
|
||||||
current_stage,
|
|
||||||
username=self.pre_user.username,
|
|
||||||
password=password,
|
|
||||||
)
|
|
||||||
if not user:
|
|
||||||
raise ValidationError("Failed to authenticate.")
|
|
||||||
self.pre_user = user
|
|
||||||
except PermissionDenied as exc:
|
|
||||||
raise ValidationError(str(exc)) from exc
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.2.1 Blueprint schema",
|
"title": "authentik 2025.2.2 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
@ -6462,6 +6462,11 @@
|
|||||||
"title": "NameID Property Mapping",
|
"title": "NameID Property Mapping",
|
||||||
"description": "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be considered"
|
"description": "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be considered"
|
||||||
},
|
},
|
||||||
|
"authn_context_class_ref_mapping": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "AuthnContextClassRef Property Mapping",
|
||||||
|
"description": "Configure how the AuthnContextClassRef value will be created. When left empty, the AuthnContextClassRef will be set based on which authentication methods the user used to authenticate."
|
||||||
|
},
|
||||||
"digest_algorithm": {
|
"digest_algorithm": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -13011,6 +13016,15 @@
|
|||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Branding favicon"
|
"title": "Branding favicon"
|
||||||
},
|
},
|
||||||
|
"branding_custom_css": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Branding custom css"
|
||||||
|
},
|
||||||
|
"branding_default_flow_background": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"title": "Branding default flow background"
|
||||||
|
},
|
||||||
"flow_authentication": {
|
"flow_authentication": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
@ -14892,9 +14906,15 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Webhook url"
|
"title": "Webhook url"
|
||||||
},
|
},
|
||||||
"webhook_mapping": {
|
"webhook_mapping_body": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"title": "Webhook mapping"
|
"title": "Webhook mapping body",
|
||||||
|
"description": "Customize the body of the request. Mapping should return data that is JSON-serializable."
|
||||||
|
},
|
||||||
|
"webhook_mapping_headers": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Webhook mapping headers",
|
||||||
|
"description": "Configure additional headers to be sent. Mapping should return a dictionary of key-value pairs"
|
||||||
},
|
},
|
||||||
"send_once": {
|
"send_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -54,7 +54,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
6
go.mod
6
go.mod
@ -11,7 +11,7 @@ require (
|
|||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10
|
github.com/go-ldap/ldap/v3 v3.4.10
|
||||||
github.com/go-openapi/runtime v0.28.0
|
github.com/go-openapi/runtime v0.28.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/handlers v1.5.2
|
github.com/gorilla/handlers v1.5.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
@ -23,13 +23,13 @@ require (
|
|||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
github.com/pires/go-proxyproto v0.8.0
|
||||||
github.com/prometheus/client_golang v1.21.1
|
github.com/prometheus/client_golang v1.21.1
|
||||||
github.com/redis/go-redis/v9 v9.7.1
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/sethvargo/go-envconfig v1.1.1
|
github.com/sethvargo/go-envconfig v1.1.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025021.4
|
goauthentik.io/api/v3 v3.2025022.5
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.28.0
|
golang.org/x/oauth2 v0.28.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.12.0
|
||||||
|
12
go.sum
12
go.sum
@ -113,8 +113,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
|||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
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-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-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -248,8 +248,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
|||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
goauthentik.io/api/v3 v3.2025021.4 h1:KFap2KW+8CwhOxjBkRnRB4flvuHEMw24+fZei9dOhzw=
|
goauthentik.io/api/v3 v3.2025022.5 h1:+yvpWu7BNvd+EXAbv6PCgjEv1UwEm9yYVIIc7d3fIlM=
|
||||||
goauthentik.io/api/v3 v3.2025021.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025022.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-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-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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.2.1"
|
const VERSION = "2025.2.2"
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
|
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
|
||||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
|
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
|
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/custom.css">
|
|
||||||
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
|
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
|
||||||
<style>
|
<style>
|
||||||
.pf-c-background-image::before {
|
.pf-c-background-image::before {
|
||||||
|
@ -62,12 +62,12 @@ function prepare_debug {
|
|||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc
|
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc
|
||||||
VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction
|
VIRTUAL_ENV=/ak-root/.venv uv sync --frozen
|
||||||
touch /unittest.xml
|
touch /unittest.xml
|
||||||
chown authentik:authentik /unittest.xml
|
chown authentik:authentik /unittest.xml
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "$(python -m authentik.lib.config debugger 2> /dev/null)" == "True" ]]; then
|
if [[ "$(python -m authentik.lib.config debugger 2>/dev/null)" == "True" ]]; then
|
||||||
prepare_debug
|
prepare_debug
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Wrapper for lifecycle/ak, to be installed by poetry"""
|
"""Wrapper for lifecycle/ak, to be installed by uv"""
|
||||||
|
|
||||||
from os import system, waitstatus_to_exitcode
|
from os import system, waitstatus_to_exitcode
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1004.0",
|
"aws-cdk": "^2.1005.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17,9 +17,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1004.0",
|
"version": "2.1005.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1004.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1005.0.tgz",
|
||||||
"integrity": "sha512-3E5ICmSc7ZCZCwLX7NY+HFmmdUYgRaL+67h/BDoDQmkhx9StC8wG4xgzHFY9k8WQS0+ib/MP28f2d9yzHtQLlQ==",
|
"integrity": "sha512-4ejfGGrGCEl0pg1xcqkxK0lpBEZqNI48wtrXhk6dYOFYPYMZtqn1kdla29ONN+eO2unewkNF4nLP1lPYhlf9Pg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1004.0",
|
"aws-cdk": "^2.1005.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.2.1
|
Default: 2025.2.2
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -616,6 +616,18 @@ msgstr ""
|
|||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/events/models.py
|
||||||
|
msgid ""
|
||||||
|
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||||
|
"serializable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/events/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure additional headers to be sent. Mapping should return a dictionary "
|
||||||
|
"of key-value pairs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Only send notification once, for example when sending a webhook into a chat "
|
"Only send notification once, for example when sending a webhook into a chat "
|
||||||
@ -1756,6 +1768,17 @@ msgid ""
|
|||||||
"NameIDPolicy of the incoming request will be considered"
|
"NameIDPolicy of the incoming request will be considered"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid "AuthnContextClassRef Property Mapping"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left "
|
||||||
|
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||||
|
"methods the user used to authenticate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: hours=-1;"
|
"Assertion valid not before current time + this value (Format: hours=-1;"
|
||||||
|
@ -19,7 +19,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-20 00:10+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Marc Schmitt, 2025\n"
|
"Last-Translator: Marc Schmitt, 2025\n"
|
||||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||||
@ -1956,6 +1956,20 @@ msgstr ""
|
|||||||
"Configure la manière dont la valeur NameID sera créée. Si laissé vide, la "
|
"Configure la manière dont la valeur NameID sera créée. Si laissé vide, la "
|
||||||
"NameIDPolicy de la requête entrante sera prise en compte"
|
"NameIDPolicy de la requête entrante sera prise en compte"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid "AuthnContextClassRef Property Mapping"
|
||||||
|
msgstr "Mappage de propriété AuthnContextClassRef"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left "
|
||||||
|
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||||
|
"methods the user used to authenticate."
|
||||||
|
msgstr ""
|
||||||
|
"Configure comment la valeur AuthnContextClassRef sera créée. Lorsque non "
|
||||||
|
"sélectionné, AuthnContextClassRef sera défini en fonction de quelle méthode "
|
||||||
|
"d'authentification l'utilisateur a utilisé pour s'authentifier."
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: "
|
"Assertion valid not before current time + this value (Format: "
|
||||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-20 00:10+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: deluxghost, 2025\n"
|
"Last-Translator: deluxghost, 2025\n"
|
||||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||||
@ -1782,6 +1782,18 @@ msgid ""
|
|||||||
"NameIDPolicy of the incoming request will be considered"
|
"NameIDPolicy of the incoming request will be considered"
|
||||||
msgstr "配置如何创建 NameID 值。如果留空,将考虑传入请求的 NameIDPolicy"
|
msgstr "配置如何创建 NameID 值。如果留空,将考虑传入请求的 NameIDPolicy"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid "AuthnContextClassRef Property Mapping"
|
||||||
|
msgstr "AuthnContextClassRef 属性映射"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left "
|
||||||
|
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||||
|
"methods the user used to authenticate."
|
||||||
|
msgstr ""
|
||||||
|
"配置如何创建 AuthnContextClassRef 值。留空时,AuthnContextClassRef 会基于用户使用的身份验证方式设置。"
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: "
|
"Assertion valid not before current time + this value (Format: "
|
||||||
|
Binary file not shown.
@ -14,7 +14,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-20 00:10+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: deluxghost, 2025\n"
|
"Last-Translator: deluxghost, 2025\n"
|
||||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||||
@ -1781,6 +1781,18 @@ msgid ""
|
|||||||
"NameIDPolicy of the incoming request will be considered"
|
"NameIDPolicy of the incoming request will be considered"
|
||||||
msgstr "配置如何创建 NameID 值。如果留空,将考虑传入请求的 NameIDPolicy"
|
msgstr "配置如何创建 NameID 值。如果留空,将考虑传入请求的 NameIDPolicy"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid "AuthnContextClassRef Property Mapping"
|
||||||
|
msgstr "AuthnContextClassRef 属性映射"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left "
|
||||||
|
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||||
|
"methods the user used to authenticate."
|
||||||
|
msgstr ""
|
||||||
|
"配置如何创建 AuthnContextClassRef 值。留空时,AuthnContextClassRef 会基于用户使用的身份验证方式设置。"
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: "
|
"Assertion valid not before current time + this value (Format: "
|
||||||
|
12
package-lock.json
generated
Normal file
12
package-lock.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@goauthentik/authentik",
|
||||||
|
"version": "2025.2.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@goauthentik/authentik",
|
||||||
|
"version": "2025.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.2.1",
|
"version": "2025.2.2",
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
6120
poetry.lock
generated
6120
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
222
pyproject.toml
222
pyproject.toml
@ -1,8 +1,116 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2025.2.1"
|
version = "2025.2.2"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||||
|
requires-python = "==3.12.*"
|
||||||
|
dependencies = [
|
||||||
|
"argon2-cffi",
|
||||||
|
"celery",
|
||||||
|
"channels",
|
||||||
|
"channels-redis",
|
||||||
|
"cryptography",
|
||||||
|
"dacite",
|
||||||
|
"deepmerge",
|
||||||
|
"defusedxml",
|
||||||
|
"django",
|
||||||
|
"django-countries",
|
||||||
|
"django-cte",
|
||||||
|
"django-filter",
|
||||||
|
"django-guardian",
|
||||||
|
"django-model-utils",
|
||||||
|
"django-pglock",
|
||||||
|
"django-prometheus",
|
||||||
|
"django-redis",
|
||||||
|
"django-storages[s3]",
|
||||||
|
"django-tenants",
|
||||||
|
"djangorestframework ==3.14.0",
|
||||||
|
"djangorestframework-guardian",
|
||||||
|
"docker",
|
||||||
|
"drf-orjson-renderer",
|
||||||
|
"drf-spectacular",
|
||||||
|
"dumb-init",
|
||||||
|
"duo-client",
|
||||||
|
"fido2",
|
||||||
|
"flower",
|
||||||
|
"geoip2",
|
||||||
|
"geopy",
|
||||||
|
"google-api-python-client",
|
||||||
|
"gssapi",
|
||||||
|
"gunicorn",
|
||||||
|
"jsonpatch",
|
||||||
|
"jwcrypto",
|
||||||
|
"kubernetes",
|
||||||
|
"ldap3",
|
||||||
|
"lxml",
|
||||||
|
"msgraph-sdk",
|
||||||
|
"opencontainers",
|
||||||
|
"packaging",
|
||||||
|
"paramiko",
|
||||||
|
"psycopg[c]",
|
||||||
|
"pydantic",
|
||||||
|
"pydantic-scim",
|
||||||
|
"pyjwt",
|
||||||
|
"pyrad",
|
||||||
|
"python-kadmin-rs ==0.5.3",
|
||||||
|
"pyyaml",
|
||||||
|
"requests-oauthlib",
|
||||||
|
"scim2-filter-parser",
|
||||||
|
"sentry-sdk",
|
||||||
|
"service_identity",
|
||||||
|
"setproctitle",
|
||||||
|
"structlog",
|
||||||
|
"swagger-spec-validator",
|
||||||
|
"tenant-schemas-celery",
|
||||||
|
"twilio",
|
||||||
|
"ua-parser",
|
||||||
|
"unidecode",
|
||||||
|
"urllib3 <3",
|
||||||
|
"uvicorn[standard]",
|
||||||
|
"watchdog",
|
||||||
|
"webauthn",
|
||||||
|
"wsproto",
|
||||||
|
"xmlsec <= 1.3.14",
|
||||||
|
"zxcvbn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"aws-cdk-lib",
|
||||||
|
"bandit",
|
||||||
|
"black",
|
||||||
|
"bump2version",
|
||||||
|
"channels[daphne]",
|
||||||
|
"codespell",
|
||||||
|
"colorama",
|
||||||
|
"constructs",
|
||||||
|
"coverage[toml]",
|
||||||
|
"debugpy",
|
||||||
|
"drf-jsonschema-serializer",
|
||||||
|
"freezegun",
|
||||||
|
"importlib-metadata",
|
||||||
|
"k5test",
|
||||||
|
"pdoc",
|
||||||
|
"pytest",
|
||||||
|
"pytest-django",
|
||||||
|
"pytest-github-actions-annotate-failures",
|
||||||
|
"pytest-randomly",
|
||||||
|
"pytest-timeout",
|
||||||
|
"requests-mock",
|
||||||
|
"ruff",
|
||||||
|
"selenium",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
django-tenants = { git = "https://github.com/rissson/django-tenants.git", branch = "authentik-fixes" }
|
||||||
|
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "20d69d9cc50a0fef31605b46f06da0c94f1ec3cf" }
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
ak = "lifecycle.ak:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.bandit]
|
[tool.bandit]
|
||||||
exclude_dirs = ["**/node_modules/**"]
|
exclude_dirs = ["**/node_modules/**"]
|
||||||
@ -30,6 +138,7 @@ skip = [
|
|||||||
]
|
]
|
||||||
dictionary = ".github/codespell-dictionary.txt,-"
|
dictionary = ".github/codespell-dictionary.txt,-"
|
||||||
ignore-words = ".github/codespell-words.txt"
|
ignore-words = ".github/codespell-words.txt"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = ['py312']
|
target-version = ['py312']
|
||||||
@ -60,6 +169,7 @@ select = [
|
|||||||
ignore = [
|
ignore = [
|
||||||
"DJ001", # Avoid using `null=True` on string-based fields,
|
"DJ001", # Avoid using `null=True` on string-based fields,
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.pylint]
|
[tool.ruff.lint.pylint]
|
||||||
max-args = 7
|
max-args = 7
|
||||||
max-branches = 18
|
max-branches = 18
|
||||||
@ -107,109 +217,3 @@ filterwarnings = [
|
|||||||
"ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning",
|
"ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning",
|
||||||
"ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",
|
"ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
argon2-cffi = "*"
|
|
||||||
celery = "*"
|
|
||||||
channels = "*"
|
|
||||||
channels-redis = "*"
|
|
||||||
cryptography = "*"
|
|
||||||
dacite = "*"
|
|
||||||
deepmerge = "*"
|
|
||||||
defusedxml = "*"
|
|
||||||
django = "*"
|
|
||||||
django-countries = "*"
|
|
||||||
django-cte = "*"
|
|
||||||
django-filter = "*"
|
|
||||||
django-guardian = "*"
|
|
||||||
django-model-utils = "*"
|
|
||||||
django-pglock = "*"
|
|
||||||
django-prometheus = "*"
|
|
||||||
django-redis = "*"
|
|
||||||
django-storages = { extras = ["s3"], version = "*" }
|
|
||||||
# See https://github.com/django-tenants/django-tenants/pull/997
|
|
||||||
django-tenants = { git = "https://github.com/rissson/django-tenants.git", branch = "authentik-fixes" }
|
|
||||||
djangorestframework = "3.14.0"
|
|
||||||
djangorestframework-guardian = "*"
|
|
||||||
docker = "*"
|
|
||||||
drf-orjson-renderer = "*"
|
|
||||||
drf-spectacular = "*"
|
|
||||||
dumb-init = "*"
|
|
||||||
duo-client = "*"
|
|
||||||
fido2 = "*"
|
|
||||||
flower = "*"
|
|
||||||
geoip2 = "*"
|
|
||||||
geopy = "*"
|
|
||||||
google-api-python-client = "*"
|
|
||||||
gunicorn = "*"
|
|
||||||
gssapi = "*"
|
|
||||||
jsonpatch = "*"
|
|
||||||
jwcrypto = "*"
|
|
||||||
kubernetes = "*"
|
|
||||||
ldap3 = "*"
|
|
||||||
lxml = "*"
|
|
||||||
msgraph-sdk = "*"
|
|
||||||
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "20d69d9cc50a0fef31605b46f06da0c94f1ec3cf", extras = [
|
|
||||||
"reggie",
|
|
||||||
] }
|
|
||||||
packaging = "*"
|
|
||||||
paramiko = "*"
|
|
||||||
psycopg = { extras = ["c"], version = "*" }
|
|
||||||
pydantic = "*"
|
|
||||||
pydantic-scim = "*"
|
|
||||||
pyjwt = "*"
|
|
||||||
pyrad = "*"
|
|
||||||
python = "~3.12"
|
|
||||||
python-kadmin-rs = "0.5.3"
|
|
||||||
pyyaml = "*"
|
|
||||||
requests-oauthlib = "*"
|
|
||||||
scim2-filter-parser = "*"
|
|
||||||
sentry-sdk = "*"
|
|
||||||
service_identity = "*"
|
|
||||||
setproctitle = "*"
|
|
||||||
structlog = "*"
|
|
||||||
swagger-spec-validator = "*"
|
|
||||||
tenant-schemas-celery = "*"
|
|
||||||
twilio = "*"
|
|
||||||
ua-parser = "*"
|
|
||||||
unidecode = "*"
|
|
||||||
# Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937
|
|
||||||
urllib3 = { extras = ["secure"], version = "<3" }
|
|
||||||
uvicorn = { extras = ["standard"], version = "*" }
|
|
||||||
watchdog = "*"
|
|
||||||
webauthn = "*"
|
|
||||||
wsproto = "*"
|
|
||||||
xmlsec = "*"
|
|
||||||
zxcvbn = "*"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
aws-cdk-lib = "*"
|
|
||||||
bandit = "*"
|
|
||||||
black = "*"
|
|
||||||
bump2version = "*"
|
|
||||||
channels = { version = "*", extras = ["daphne"] }
|
|
||||||
codespell = "*"
|
|
||||||
colorama = "*"
|
|
||||||
constructs = "*"
|
|
||||||
coverage = { extras = ["toml"], version = "*" }
|
|
||||||
debugpy = "*"
|
|
||||||
drf-jsonschema-serializer = "*"
|
|
||||||
freezegun = "*"
|
|
||||||
importlib-metadata = "*"
|
|
||||||
k5test = "*"
|
|
||||||
pdoc = "*"
|
|
||||||
pytest = "*"
|
|
||||||
pytest-django = "*"
|
|
||||||
pytest-github-actions-annotate-failures = "*"
|
|
||||||
pytest-randomly = "*"
|
|
||||||
pytest-timeout = "*"
|
|
||||||
requests-mock = "*"
|
|
||||||
ruff = "*"
|
|
||||||
selenium = "*"
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
|
||||||
ak = "lifecycle.ak:main"
|
|
||||||
|
90
schema.yml
90
schema.yml
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2025.2.1
|
version: 2025.2.2
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
@ -4447,6 +4447,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
- in: query
|
||||||
|
name: branding_default_flow_background
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- in: query
|
- in: query
|
||||||
name: branding_favicon
|
name: branding_favicon
|
||||||
schema:
|
schema:
|
||||||
@ -22191,6 +22195,11 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
- in: query
|
||||||
|
name: authn_context_class_ref_mapping
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
- in: query
|
- in: query
|
||||||
name: authorization_flow
|
name: authorization_flow
|
||||||
schema:
|
schema:
|
||||||
@ -25745,7 +25754,7 @@ paths:
|
|||||||
description: ''
|
description: ''
|
||||||
delete:
|
delete:
|
||||||
operationId: sources_all_destroy
|
operationId: sources_all_destroy
|
||||||
description: Source Viewset
|
description: Prevent deletion of built-in sources
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: slug
|
name: slug
|
||||||
@ -41140,6 +41149,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
branding_favicon:
|
branding_favicon:
|
||||||
type: string
|
type: string
|
||||||
|
branding_custom_css:
|
||||||
|
type: string
|
||||||
|
branding_default_flow_background:
|
||||||
|
type: string
|
||||||
flow_authentication:
|
flow_authentication:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@ -41199,6 +41212,11 @@ components:
|
|||||||
branding_favicon:
|
branding_favicon:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
branding_custom_css:
|
||||||
|
type: string
|
||||||
|
branding_default_flow_background:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
flow_authentication:
|
flow_authentication:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@ -42091,6 +42109,8 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
branding_favicon:
|
branding_favicon:
|
||||||
type: string
|
type: string
|
||||||
|
branding_custom_css:
|
||||||
|
type: string
|
||||||
ui_footer_links:
|
ui_footer_links:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -42117,6 +42137,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
|
- branding_custom_css
|
||||||
- branding_favicon
|
- branding_favicon
|
||||||
- branding_logo
|
- branding_logo
|
||||||
- branding_title
|
- branding_title
|
||||||
@ -46869,10 +46890,18 @@ components:
|
|||||||
webhook_url:
|
webhook_url:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
webhook_mapping:
|
webhook_mapping_body:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
|
description: Customize the body of the request. Mapping should return data
|
||||||
|
that is JSON-serializable.
|
||||||
|
webhook_mapping_headers:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: Configure additional headers to be sent. Mapping should return
|
||||||
|
a dictionary of key-value pairs
|
||||||
send_once:
|
send_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Only send notification once, for example when sending a webhook
|
description: Only send notification once, for example when sending a webhook
|
||||||
@ -46900,10 +46929,18 @@ components:
|
|||||||
webhook_url:
|
webhook_url:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
webhook_mapping:
|
webhook_mapping_body:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
|
description: Customize the body of the request. Mapping should return data
|
||||||
|
that is JSON-serializable.
|
||||||
|
webhook_mapping_headers:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: Configure additional headers to be sent. Mapping should return
|
||||||
|
a dictionary of key-value pairs
|
||||||
send_once:
|
send_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Only send notification once, for example when sending a webhook
|
description: Only send notification once, for example when sending a webhook
|
||||||
@ -50120,6 +50157,11 @@ components:
|
|||||||
branding_favicon:
|
branding_favicon:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
branding_custom_css:
|
||||||
|
type: string
|
||||||
|
branding_default_flow_background:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
flow_authentication:
|
flow_authentication:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@ -51332,10 +51374,18 @@ components:
|
|||||||
webhook_url:
|
webhook_url:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
webhook_mapping:
|
webhook_mapping_body:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
|
description: Customize the body of the request. Mapping should return data
|
||||||
|
that is JSON-serializable.
|
||||||
|
webhook_mapping_headers:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
description: Configure additional headers to be sent. Mapping should return
|
||||||
|
a dictionary of key-value pairs
|
||||||
send_once:
|
send_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Only send notification once, for example when sending a webhook
|
description: Only send notification once, for example when sending a webhook
|
||||||
@ -52228,6 +52278,14 @@ components:
|
|||||||
title: NameID Property Mapping
|
title: NameID Property Mapping
|
||||||
description: Configure how the NameID value will be created. When left empty,
|
description: Configure how the NameID value will be created. When left empty,
|
||||||
the NameIDPolicy of the incoming request will be considered
|
the NameIDPolicy of the incoming request will be considered
|
||||||
|
authn_context_class_ref_mapping:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
title: AuthnContextClassRef Property Mapping
|
||||||
|
description: Configure how the AuthnContextClassRef value will be created.
|
||||||
|
When left empty, the AuthnContextClassRef will be set based on which authentication
|
||||||
|
methods the user used to authenticate.
|
||||||
digest_algorithm:
|
digest_algorithm:
|
||||||
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
||||||
signature_algorithm:
|
signature_algorithm:
|
||||||
@ -55183,6 +55241,14 @@ components:
|
|||||||
title: NameID Property Mapping
|
title: NameID Property Mapping
|
||||||
description: Configure how the NameID value will be created. When left empty,
|
description: Configure how the NameID value will be created. When left empty,
|
||||||
the NameIDPolicy of the incoming request will be considered
|
the NameIDPolicy of the incoming request will be considered
|
||||||
|
authn_context_class_ref_mapping:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
title: AuthnContextClassRef Property Mapping
|
||||||
|
description: Configure how the AuthnContextClassRef value will be created.
|
||||||
|
When left empty, the AuthnContextClassRef will be set based on which authentication
|
||||||
|
methods the user used to authenticate.
|
||||||
digest_algorithm:
|
digest_algorithm:
|
||||||
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
||||||
signature_algorithm:
|
signature_algorithm:
|
||||||
@ -55348,6 +55414,14 @@ components:
|
|||||||
title: NameID Property Mapping
|
title: NameID Property Mapping
|
||||||
description: Configure how the NameID value will be created. When left empty,
|
description: Configure how the NameID value will be created. When left empty,
|
||||||
the NameIDPolicy of the incoming request will be considered
|
the NameIDPolicy of the incoming request will be considered
|
||||||
|
authn_context_class_ref_mapping:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
title: AuthnContextClassRef Property Mapping
|
||||||
|
description: Configure how the AuthnContextClassRef value will be created.
|
||||||
|
When left empty, the AuthnContextClassRef will be set based on which authentication
|
||||||
|
methods the user used to authenticate.
|
||||||
digest_algorithm:
|
digest_algorithm:
|
||||||
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
$ref: '#/components/schemas/DigestAlgorithmEnum'
|
||||||
signature_algorithm:
|
signature_algorithm:
|
||||||
@ -55897,7 +55971,10 @@ components:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
provider:
|
provider:
|
||||||
type: integer
|
type: integer
|
||||||
|
attributes:
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
|
- attributes
|
||||||
- group
|
- group
|
||||||
- group_obj
|
- group_obj
|
||||||
- id
|
- id
|
||||||
@ -55984,7 +56061,10 @@ components:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
provider:
|
provider:
|
||||||
type: integer
|
type: integer
|
||||||
|
attributes:
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
|
- attributes
|
||||||
- id
|
- id
|
||||||
- provider
|
- provider
|
||||||
- scim_id
|
- scim_id
|
||||||
|
@ -7,6 +7,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
POSTGRES_DB: authentik
|
POSTGRES_DB: authentik
|
||||||
|
command:
|
||||||
|
["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"]
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:5432:5432
|
- 127.0.0.1:5432:5432
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -47,10 +47,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
"GF_AUTH_GITHUB_AUTH_URL": self.url(
|
"GF_AUTH_GITHUB_AUTH_URL": self.url(
|
||||||
"authentik_providers_oauth2_root:github-authorize"
|
"authentik_providers_oauth2_root:github-authorize"
|
||||||
),
|
),
|
||||||
"GF_AUTH_GITHUB_TOKEN_URL": self.url(
|
"GF_AUTH_GITHUB_TOKEN_URL": self.host_url(
|
||||||
"authentik_providers_oauth2_root:github-access-token"
|
"authentik_providers_oauth2_root:github-access-token"
|
||||||
),
|
),
|
||||||
"GF_AUTH_GITHUB_API_URL": self.url("authentik_providers_oauth2_root:github-user"),
|
"GF_AUTH_GITHUB_API_URL": self.host_url(
|
||||||
|
"authentik_providers_oauth2_root:github-user"
|
||||||
|
),
|
||||||
"GF_LOG_LEVEL": "debug",
|
"GF_LOG_LEVEL": "debug",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -53,8 +53,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
|
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
|
||||||
"GF_AUTH_GENERIC_OAUTH_SCOPES": "openid email profile",
|
"GF_AUTH_GENERIC_OAUTH_SCOPES": "openid email profile",
|
||||||
"GF_AUTH_GENERIC_OAUTH_AUTH_URL": self.url("authentik_providers_oauth2:authorize"),
|
"GF_AUTH_GENERIC_OAUTH_AUTH_URL": self.url("authentik_providers_oauth2:authorize"),
|
||||||
"GF_AUTH_GENERIC_OAUTH_TOKEN_URL": self.url("authentik_providers_oauth2:token"),
|
"GF_AUTH_GENERIC_OAUTH_TOKEN_URL": self.host_url(
|
||||||
"GF_AUTH_GENERIC_OAUTH_API_URL": self.url("authentik_providers_oauth2:userinfo"),
|
"authentik_providers_oauth2:token"
|
||||||
|
),
|
||||||
|
"GF_AUTH_GENERIC_OAUTH_API_URL": self.host_url(
|
||||||
|
"authentik_providers_oauth2:userinfo"
|
||||||
|
),
|
||||||
"GF_AUTH_SIGNOUT_REDIRECT_URL": self.url(
|
"GF_AUTH_SIGNOUT_REDIRECT_URL": self.url(
|
||||||
"authentik_providers_oauth2:end-session",
|
"authentik_providers_oauth2:end-session",
|
||||||
application_slug=self.app_slug,
|
application_slug=self.app_slug,
|
||||||
|
@ -42,7 +42,9 @@ class TestSourceSCIM(SeleniumTestCase):
|
|||||||
test_launch = session.post(
|
test_launch = session.post(
|
||||||
"http://localhost:8080/test/run",
|
"http://localhost:8080/test/run",
|
||||||
data={
|
data={
|
||||||
"endPoint": self.live_server_url + f"/source/scim/{source.slug}/v2",
|
"endPoint": self.host_url(
|
||||||
|
"authentik_sources_scim:v2-root", source_slug=source.slug
|
||||||
|
),
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"password": source.token.key,
|
"password": source.token.key,
|
||||||
"jwtToken": None,
|
"jwtToken": None,
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from functools import lru_cache, wraps
|
from functools import lru_cache, wraps
|
||||||
from os import environ
|
from os import environ
|
||||||
@ -36,8 +35,8 @@ from authentik.core.models import User
|
|||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
RETRIES = int(environ.get("RETRIES", "3"))
|
|
||||||
IS_CI = "CI" in environ
|
IS_CI = "CI" in environ
|
||||||
|
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
|
||||||
|
|
||||||
|
|
||||||
def get_docker_tag() -> str:
|
def get_docker_tag() -> str:
|
||||||
@ -51,13 +50,6 @@ def get_docker_tag() -> str:
|
|||||||
return f"gh-{branch_name}"
|
return f"gh-{branch_name}"
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip() -> str:
|
|
||||||
"""Get the local machine's IP"""
|
|
||||||
hostname = socket.gethostname()
|
|
||||||
ip_addr = socket.gethostbyname(hostname)
|
|
||||||
return ip_addr
|
|
||||||
|
|
||||||
|
|
||||||
class DockerTestCase(TestCase):
|
class DockerTestCase(TestCase):
|
||||||
"""Mixin for dealing with containers"""
|
"""Mixin for dealing with containers"""
|
||||||
|
|
||||||
@ -113,6 +105,9 @@ class DockerTestCase(TestCase):
|
|||||||
specs["network"] = self.__network.name
|
specs["network"] = self.__network.name
|
||||||
specs["labels"] = self.docker_labels
|
specs["labels"] = self.docker_labels
|
||||||
specs["detach"] = True
|
specs["detach"] = True
|
||||||
|
specs["extra_hosts"] = {
|
||||||
|
"host.docker.internal": "host-gateway",
|
||||||
|
}
|
||||||
if hasattr(self, "live_server_url"):
|
if hasattr(self, "live_server_url"):
|
||||||
specs.setdefault("environment", {})
|
specs.setdefault("environment", {})
|
||||||
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
|
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
|
||||||
@ -155,7 +150,7 @@ class DockerTestCase(TestCase):
|
|||||||
class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||||
"""StaticLiveServerTestCase which automatically creates a Webdriver instance"""
|
"""StaticLiveServerTestCase which automatically creates a Webdriver instance"""
|
||||||
|
|
||||||
host = get_local_ip()
|
host = "0.0.0.0" # nosec Required for containers to reach us directly on the host
|
||||||
wait_timeout: int
|
wait_timeout: int
|
||||||
user: User
|
user: User
|
||||||
|
|
||||||
@ -210,6 +205,15 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
|
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def host_url(self, view, query: dict | None = None, **kwargs) -> str:
|
||||||
|
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
||||||
|
url = f"http://host.docker.internal:{self.server_thread.port}" + reverse(
|
||||||
|
view, kwargs=kwargs
|
||||||
|
)
|
||||||
|
if query:
|
||||||
|
return url + "?" + urlencode(query)
|
||||||
|
return url
|
||||||
|
|
||||||
def url(self, view, query: dict | None = None, **kwargs) -> str:
|
def url(self, view, query: dict | None = None, **kwargs) -> str:
|
||||||
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
||||||
url = self.live_server_url + reverse(view, kwargs=kwargs)
|
url = self.live_server_url + reverse(view, kwargs=kwargs)
|
||||||
|
@ -19,5 +19,5 @@
|
|||||||
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
"importOrderSortSpecifiers": true,
|
"importOrderSortSpecifiers": true,
|
||||||
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
|
"importOrderParserPlugins": ["typescript", "jsx", "classProperties", "decorators-legacy"]
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ const importInlinePatterns = [
|
|||||||
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
|
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
|
||||||
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
|
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
|
||||||
'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css',
|
'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css',
|
||||||
|
'import OneDark from "@goauthentik/common/styles/one-dark\\.css',
|
||||||
'import styles from "\\./LibraryPageImpl\\.css',
|
'import styles from "\\./LibraryPageImpl\\.css',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -39,6 +40,10 @@ const config: StorybookConfig = {
|
|||||||
from: "../src/common/styles/theme-dark.css",
|
from: "../src/common/styles/theme-dark.css",
|
||||||
to: "@goauthentik/common/styles/theme-dark.css",
|
to: "@goauthentik/common/styles/theme-dark.css",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: "../src/common/styles/one-dark.css",
|
||||||
|
to: "@goauthentik/common/styles/one-dark.css",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
framework: {
|
framework: {
|
||||||
name: "@storybook/web-components-vite",
|
name: "@storybook/web-components-vite",
|
||||||
|
@ -71,7 +71,7 @@ export default [
|
|||||||
...globals.node,
|
...globals.node,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
files: ["scripts/**/*.mjs", "*.ts", "*.mjs"],
|
||||||
rules: {
|
rules: {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
// We WANT our scripts to output to the console!
|
// We WANT our scripts to output to the console!
|
||||||
|
2772
web/package-lock.json
generated
2772
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
|||||||
"name": "@goauthentik/web",
|
"name": "@goauthentik/web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-python": "^6.1.6",
|
"@codemirror/lang-python": "^6.1.6",
|
||||||
@ -11,12 +12,13 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.5.7",
|
"@formatjs/intl-listformat": "^7.5.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@goauthentik/api": "^2025.2.1-1741798605",
|
"@goauthentik/api": "^2025.2.2-1742585853",
|
||||||
"@lit-labs/ssr": "^3.2.2",
|
"@lit-labs/ssr": "^3.2.2",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
"@lit/task": "^1.0.1",
|
"@lit/task": "^1.0.1",
|
||||||
|
"@mdx-js/mdx": "^3.1.0",
|
||||||
"@open-wc/lit-helpers": "^0.7.0",
|
"@open-wc/lit-helpers": "^0.7.0",
|
||||||
"@patternfly/elements": "^4.0.2",
|
"@patternfly/elements": "^4.0.2",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
@ -24,6 +26,7 @@
|
|||||||
"@spotlightjs/spotlight": "^2.4.2",
|
"@spotlightjs/spotlight": "^2.4.2",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"change-case": "^5.4.4",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
"chartjs-adapter-date-fns": "^3.0.0",
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
@ -31,16 +34,28 @@
|
|||||||
"core-js": "^3.38.1",
|
"core-js": "^3.38.1",
|
||||||
"country-flag-icons": "^1.5.13",
|
"country-flag-icons": "^1.5.13",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"deepmerge-ts": "^7.1.5",
|
||||||
"dompurify": "^3.2.4",
|
"dompurify": "^3.2.4",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"guacamole-common-js": "^1.5.0",
|
"guacamole-common-js": "^1.5.0",
|
||||||
|
"hastscript": "^9.0.1",
|
||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"md-front-matter": "^1.0.4",
|
"md-front-matter": "^1.0.4",
|
||||||
"mermaid": "^11.4.1",
|
"mermaid": "^11.4.1",
|
||||||
"rapidoc": "^9.3.7",
|
"rapidoc": "^9.3.7",
|
||||||
"showdown": "^2.1.0",
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"rehype-highlight": "^7.0.2",
|
||||||
|
"rehype-mermaid": "^3.0.0",
|
||||||
|
"rehype-parse": "^9.0.1",
|
||||||
|
"rehype-stringify": "^10.0.1",
|
||||||
|
"remark-directive": "^4.0.0",
|
||||||
|
"remark-frontmatter": "^5.0.0",
|
||||||
|
"remark-gfm": "^4.0.1",
|
||||||
|
"remark-mdx-frontmatter": "^5.0.0",
|
||||||
"style-mod": "^4.1.2",
|
"style-mod": "^4.1.2",
|
||||||
"ts-pattern": "^5.4.0",
|
"ts-pattern": "^5.4.0",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
"webcomponent-qr-code": "^1.2.0",
|
"webcomponent-qr-code": "^1.2.0",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1"
|
||||||
},
|
},
|
||||||
@ -66,15 +81,17 @@
|
|||||||
"@types/guacamole-common-js": "^1.5.2",
|
"@types/guacamole-common-js": "^1.5.2",
|
||||||
"@types/mocha": "^10.0.8",
|
"@types/mocha": "^10.0.8",
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
"@types/showdown": "^2.0.6",
|
"@types/react": "^18.3.13",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||||
"@typescript-eslint/parser": "^8.8.0",
|
"@typescript-eslint/parser": "^8.8.0",
|
||||||
"@wdio/browser-runner": "9.4",
|
"@wdio/browser-runner": "9.4",
|
||||||
"@wdio/cli": "9.4",
|
"@wdio/cli": "9.4",
|
||||||
"@wdio/spec-reporter": "^9.1.2",
|
"@wdio/spec-reporter": "^9.1.2",
|
||||||
"chokidar": "^4.0.1",
|
|
||||||
"chromedriver": "^131.0.1",
|
"chromedriver": "^131.0.1",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
|
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
"eslint-plugin-lit": "^1.15.0",
|
"eslint-plugin-lit": "^1.15.0",
|
||||||
"eslint-plugin-wc": "^2.1.1",
|
"eslint-plugin-wc": "^2.1.1",
|
||||||
@ -118,7 +135,7 @@
|
|||||||
"build-locales:build": "wireit",
|
"build-locales:build": "wireit",
|
||||||
"build-proxy": "wireit",
|
"build-proxy": "wireit",
|
||||||
"build:sfe": "wireit",
|
"build:sfe": "wireit",
|
||||||
"esbuild:watch": "node build.mjs --watch",
|
"esbuild:watch": "node scripts/build-web.mjs --watch",
|
||||||
"extract-locales": "wireit",
|
"extract-locales": "wireit",
|
||||||
"format": "wireit",
|
"format": "wireit",
|
||||||
"lint": "wireit",
|
"lint": "wireit",
|
||||||
@ -153,7 +170,7 @@
|
|||||||
"instead of `npm run watch`. The former is more comprehensive, but ",
|
"instead of `npm run watch`. The former is more comprehensive, but ",
|
||||||
"the latter is faster."
|
"the latter is faster."
|
||||||
],
|
],
|
||||||
"command": "${NODE_RUNNER} build.mjs",
|
"command": "${NODE_RUNNER} scripts/build-web.mjs",
|
||||||
"files": [
|
"files": [
|
||||||
"src/**/*.{css,jpg,png,ts,js,json}",
|
"src/**/*.{css,jpg,png,ts,js,json}",
|
||||||
"!src/**/*.stories.ts",
|
"!src/**/*.stories.ts",
|
||||||
@ -171,8 +188,8 @@
|
|||||||
"./dist/enterprise/**",
|
"./dist/enterprise/**",
|
||||||
"./dist/poly-*.js",
|
"./dist/poly-*.js",
|
||||||
"./dist/poly-*.js.map",
|
"./dist/poly-*.js.map",
|
||||||
"./dist/custom.css",
|
|
||||||
"./dist/theme-dark.css",
|
"./dist/theme-dark.css",
|
||||||
|
"./dist/one-dark.css",
|
||||||
"./dist/patternfly.min.css"
|
"./dist/patternfly.min.css"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -195,7 +212,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"build-proxy": {
|
"build-proxy": {
|
||||||
"command": "node build.mjs --proxy",
|
"command": "node scripts/build-web.mjs --proxy",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"build-locales"
|
"build-locales"
|
||||||
]
|
]
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { execFileSync } from "child_process";
|
import { execFileSync } from "child_process";
|
||||||
|
import { deepmerge } from "deepmerge-ts";
|
||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
|
import { polyfillNode } from "esbuild-plugin-polyfill-node";
|
||||||
import findFreePorts from "find-free-ports";
|
import findFreePorts from "find-free-ports";
|
||||||
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
|
import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs";
|
||||||
import { globSync } from "glob";
|
import { globSync } from "glob";
|
||||||
import path from "path";
|
import * as path from "path";
|
||||||
import { cwd } from "process";
|
import { cwd } from "process";
|
||||||
import process from "process";
|
import process from "process";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
import { buildObserverPlugin } from "./build-observer-plugin.mjs";
|
import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs";
|
||||||
|
import { buildObserverPlugin } from "./esbuild/build-observer-plugin.mjs";
|
||||||
|
|
||||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||||
let authentikProjectRoot = __dirname + "../";
|
let authentikProjectRoot = path.join(__dirname, "..", "..");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the package.json file in the root folder, as it has the current version information.
|
// Use the package.json file in the root folder, as it has the current version information.
|
||||||
@ -51,7 +54,6 @@ const definitions = Object.fromEntries(
|
|||||||
const assetsFileMappings = [
|
const assetsFileMappings = [
|
||||||
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
||||||
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
|
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
|
||||||
["src/custom.css", "."],
|
|
||||||
["src/common/styles/**", "."],
|
["src/common/styles/**", "."],
|
||||||
["src/assets/images/**", "./assets/images"],
|
["src/assets/images/**", "./assets/images"],
|
||||||
["./icons/*", "./assets/icons"],
|
["./icons/*", "./assets/icons"],
|
||||||
@ -108,7 +110,7 @@ const entryPoints = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @satisfies {import("esbuild").BuildOptions}
|
* @type {import("esbuild").BuildOptions}
|
||||||
*/
|
*/
|
||||||
const BASE_ESBUILD_OPTIONS = {
|
const BASE_ESBUILD_OPTIONS = {
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@ -121,12 +123,19 @@ const BASE_ESBUILD_OPTIONS = {
|
|||||||
tsconfig: "./tsconfig.json",
|
tsconfig: "./tsconfig.json",
|
||||||
loader: {
|
loader: {
|
||||||
".css": "text",
|
".css": "text",
|
||||||
".md": "text",
|
|
||||||
".mdx": "text",
|
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
polyfillNode({
|
||||||
|
polyfills: {
|
||||||
|
path: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
mdxPlugin({
|
||||||
|
root: authentikProjectRoot,
|
||||||
|
}),
|
||||||
|
],
|
||||||
define: definitions,
|
define: definitions,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
plugins: [],
|
|
||||||
logOverride: {
|
logOverride: {
|
||||||
/**
|
/**
|
||||||
* HACK: Silences issue originating in ESBuild.
|
* HACK: Silences issue originating in ESBuild.
|
||||||
@ -161,24 +170,35 @@ function composeVersionID() {
|
|||||||
* @throws {Error} on build failure
|
* @throws {Error} on build failure
|
||||||
*/
|
*/
|
||||||
function createEntryPointOptions([source, dest], overrides = {}) {
|
function createEntryPointOptions([source, dest], overrides = {}) {
|
||||||
const outdir = path.join(__dirname, "./dist", dest);
|
const outdir = path.join(__dirname, "..", "dist", dest);
|
||||||
|
|
||||||
return {
|
/**
|
||||||
...BASE_ESBUILD_OPTIONS,
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const entryPointConfig = {
|
||||||
entryPoints: [`./src/${source}`],
|
entryPoints: [`./src/${source}`],
|
||||||
entryNames: `[dir]/[name]-${composeVersionID()}`,
|
entryNames: `[dir]/[name]-${composeVersionID()}`,
|
||||||
|
publicPath: path.join("/static", "dist", dest),
|
||||||
outdir,
|
outdir,
|
||||||
...overrides,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
|
const mergedConfig = deepmerge(BASE_ESBUILD_OPTIONS, entryPointConfig, overrides);
|
||||||
|
|
||||||
|
return mergedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build all entry points in parallel.
|
* Build all entry points in parallel.
|
||||||
*
|
*
|
||||||
* @param {EntryPoint[]} entryPoints
|
* @param {EntryPoint[]} entryPoints
|
||||||
|
* @returns {Promise<esbuild.BuildResult[]>}
|
||||||
*/
|
*/
|
||||||
async function buildParallel(entryPoints) {
|
async function buildParallel(entryPoints) {
|
||||||
await Promise.allSettled(
|
return Promise.all(
|
||||||
entryPoints.map((entryPoint) => {
|
entryPoints.map((entryPoint) => {
|
||||||
return esbuild.build(createEntryPointOptions(entryPoint));
|
return esbuild.build(createEntryPointOptions(entryPoint));
|
||||||
}),
|
}),
|
||||||
@ -210,11 +230,10 @@ async function doWatch() {
|
|||||||
return esbuild.context(
|
return esbuild.context(
|
||||||
createEntryPointOptions(entryPoint, {
|
createEntryPointOptions(entryPoint, {
|
||||||
plugins: [
|
plugins: [
|
||||||
...BASE_ESBUILD_OPTIONS.plugins,
|
|
||||||
buildObserverPlugin({
|
buildObserverPlugin({
|
||||||
serverURL,
|
serverURL,
|
||||||
logPrefix: entryPoint[1],
|
logPrefix: entryPoint[1],
|
||||||
relativeRoot: __dirname,
|
relativeRoot: path.join(__dirname, ".."),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
81
web/scripts/esbuild/build-mdx-plugin.mjs
Normal file
81
web/scripts/esbuild/build-mdx-plugin.mjs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* @import {
|
||||||
|
OnLoadArgs,
|
||||||
|
OnLoadResult,
|
||||||
|
Plugin,
|
||||||
|
PluginBuild
|
||||||
|
* } from 'esbuild'
|
||||||
|
*/
|
||||||
|
import * as fs from "node:fs/promises";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Omit<OnLoadArgs, 'pluginData'> & LoadDataFields} LoadData
|
||||||
|
* Data passed to `onload`.
|
||||||
|
*
|
||||||
|
* @typedef LoadDataFields
|
||||||
|
* Extra fields given in `data` to `onload`.
|
||||||
|
* @property {PluginData | null | undefined} [pluginData]
|
||||||
|
* Plugin data.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @typedef PluginData
|
||||||
|
* Extra data passed.
|
||||||
|
* @property {Buffer | string | null | undefined} [contents]
|
||||||
|
* File contents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const name = "mdx-plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef MDXPluginOptions
|
||||||
|
*
|
||||||
|
* @property {string} root Root directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle MDX into JSON modules.
|
||||||
|
*
|
||||||
|
* @param {MDXPluginOptions} options Options.
|
||||||
|
* @returns {Plugin} Plugin.
|
||||||
|
*/
|
||||||
|
export function mdxPlugin({ root }) {
|
||||||
|
return { name, setup };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PluginBuild} build
|
||||||
|
* Build.
|
||||||
|
* @returns {undefined}
|
||||||
|
* Nothing.
|
||||||
|
*/
|
||||||
|
function setup(build) {
|
||||||
|
build.onLoad({ filter: /\.mdx?$/ }, onload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {LoadData} data
|
||||||
|
* Data.
|
||||||
|
* @returns {Promise<OnLoadResult>}
|
||||||
|
* Result.
|
||||||
|
*/
|
||||||
|
async function onload(data) {
|
||||||
|
const content = String(
|
||||||
|
data.pluginData &&
|
||||||
|
data.pluginData.contents !== null &&
|
||||||
|
data.pluginData.contents !== undefined
|
||||||
|
? data.pluginData.contents
|
||||||
|
: await fs.readFile(data.path),
|
||||||
|
);
|
||||||
|
|
||||||
|
const publicPath = path.resolve(
|
||||||
|
"/",
|
||||||
|
path.relative(path.join(root, "website"), data.path),
|
||||||
|
);
|
||||||
|
const publicDirectory = path.dirname(publicPath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
contents: JSON.stringify({ content, publicPath, publicDirectory }),
|
||||||
|
loader: "file",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import path from "path";
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function serializeCustomEventToStream(event) {
|
export function serializeCustomEventToStream(event) {
|
||||||
// @ts-ignore
|
// @ts-expect-error - TS doesn't know about the detail property
|
||||||
const data = event.detail ?? {};
|
const data = event.detail ?? {};
|
||||||
|
|
||||||
const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];
|
const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];
|
@ -5,7 +5,6 @@ import "@goauthentik/components/events/ObjectChangelog";
|
|||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/PageHeader";
|
import "@goauthentik/elements/PageHeader";
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
@ -3,7 +3,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|||||||
import MDApplication from "@goauthentik/docs/add-secure-apps/applications/index.md";
|
import MDApplication from "@goauthentik/docs/add-secure-apps/applications/index.md";
|
||||||
import "@goauthentik/elements/AppIcon.js";
|
import "@goauthentik/elements/AppIcon.js";
|
||||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||||
import "@goauthentik/elements/Markdown";
|
import "@goauthentik/elements/ak-mdx";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
import "@goauthentik/elements/forms/ModalForm";
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
@ -89,7 +89,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
|||||||
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-markdown .md=${MDApplication} meta="applications/index.md"></ak-markdown>
|
<ak-mdx .url=${MDApplication}></ak-mdx>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -94,7 +94,7 @@ export class ApplicationEntitlementsPage extends Table<ApplicationEntitlement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExpanded(item: ApplicationEntitlement): TemplateResult {
|
renderExpanded(item: ApplicationEntitlement): TemplateResult {
|
||||||
return html` <td></td>
|
return html`<td></td>
|
||||||
<td role="cell" colspan="4">
|
<td role="cell" colspan="4">
|
||||||
<div class="pf-c-table__expandable-row-content">
|
<div class="pf-c-table__expandable-row-content">
|
||||||
<div class="pf-c-content">
|
<div class="pf-c-content">
|
||||||
|
@ -51,11 +51,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
return html` <ak-form-element-horizontal
|
return html` <ak-form-element-horizontal label=${msg("Domain")} required name="domain">
|
||||||
label=${msg("Domain")}
|
|
||||||
?required=${true}
|
|
||||||
name="domain"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.domain, window.location.host)}"
|
value="${first(this.instance?.domain, window.location.host)}"
|
||||||
@ -90,14 +86,10 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-form-group .expanded=${true}>
|
<ak-form-group>
|
||||||
<span slot="header"> ${msg("Branding settings")} </span>
|
<span slot="header"> ${msg("Branding settings")} </span>
|
||||||
<div slot="body" class="pf-c-form">
|
<div slot="body" class="pf-c-form">
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
|
||||||
label=${msg("Title")}
|
|
||||||
?required=${true}
|
|
||||||
name="brandingTitle"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(
|
value="${first(
|
||||||
@ -111,11 +103,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
${msg("Branding shown in page title and several other places.")}
|
${msg("Branding shown in page title and several other places.")}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal label=${msg("Logo")} required name="brandingLogo">
|
||||||
label=${msg("Logo")}
|
|
||||||
?required=${true}
|
|
||||||
name="brandingLogo"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.brandingLogo, DefaultBrand.brandingLogo)}"
|
value="${first(this.instance?.brandingLogo, DefaultBrand.brandingLogo)}"
|
||||||
@ -130,7 +118,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Favicon")}
|
label=${msg("Favicon")}
|
||||||
?required=${true}
|
required
|
||||||
name="brandingFavicon"
|
name="brandingFavicon"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@ -148,6 +136,45 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
${msg("Icon shown in the browser tab.")}
|
${msg("Icon shown in the browser tab.")}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Default flow background")}
|
||||||
|
?required=${true}
|
||||||
|
name="brandingDefaultFlowBackground"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${first(
|
||||||
|
this.instance?.brandingDefaultFlowBackground,
|
||||||
|
"/static/dist/assets/images/flow_background.jpg",
|
||||||
|
)}"
|
||||||
|
class="pf-c-form-control pf-m-monospace"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Default background used during flow execution. Can be overridden per flow.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Custom CSS")}
|
||||||
|
required
|
||||||
|
name="brandingCustomCss"
|
||||||
|
>
|
||||||
|
<ak-codemirror
|
||||||
|
mode=${CodeMirrorMode.CSS}
|
||||||
|
value="${first(
|
||||||
|
this.instance?.brandingCustomCss,
|
||||||
|
DefaultBrand.brandingCustomCss,
|
||||||
|
)}"
|
||||||
|
>
|
||||||
|
</ak-codemirror>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Custom CSS to apply to pages when this brand is active.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
return html` <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
|
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${ifDefined(this.instance?.name)}"
|
value="${ifDefined(this.instance?.name)}"
|
||||||
@ -74,7 +74,7 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${msg("Mode")} ?required=${true} name="mode">
|
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||||
<ak-radio
|
<ak-radio
|
||||||
@change=${(ev: CustomEvent<{ value: NotificationTransportModeEnum }>) => {
|
@change=${(ev: CustomEvent<{ value: NotificationTransportModeEnum }>) => {
|
||||||
this.onModeChange(ev.detail.value);
|
this.onModeChange(ev.detail.value);
|
||||||
@ -106,7 +106,7 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||||||
?hidden=${!this.showWebhook}
|
?hidden=${!this.showWebhook}
|
||||||
label=${msg("Webhook URL")}
|
label=${msg("Webhook URL")}
|
||||||
name="webhookUrl"
|
name="webhookUrl"
|
||||||
?required=${true}
|
required
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -116,8 +116,8 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
?hidden=${!this.showWebhook}
|
?hidden=${!this.showWebhook}
|
||||||
label=${msg("Webhook Mapping")}
|
label=${msg("Webhook Body Mapping")}
|
||||||
name="webhookMapping"
|
name="webhookMappingBody"
|
||||||
>
|
>
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.fetchObjects=${async (
|
.fetchObjects=${async (
|
||||||
@ -141,9 +141,42 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||||||
return item?.pk;
|
return item?.pk;
|
||||||
}}
|
}}
|
||||||
.selected=${(item: NotificationWebhookMapping): boolean => {
|
.selected=${(item: NotificationWebhookMapping): boolean => {
|
||||||
return this.instance?.webhookMapping === item.pk;
|
return this.instance?.webhookMappingBody === item.pk;
|
||||||
}}
|
}}
|
||||||
?blankable=${true}
|
blankable
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
?hidden=${!this.showWebhook}
|
||||||
|
label=${msg("Webhook Header Mapping")}
|
||||||
|
name="webhookMappingHeaders"
|
||||||
|
>
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (
|
||||||
|
query?: string,
|
||||||
|
): Promise<NotificationWebhookMapping[]> => {
|
||||||
|
const args: PropertymappingsNotificationListRequest = {
|
||||||
|
ordering: "name",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const items = await new PropertymappingsApi(
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
).propertymappingsNotificationList(args);
|
||||||
|
return items.results;
|
||||||
|
}}
|
||||||
|
.renderElement=${(item: NotificationWebhookMapping): string => {
|
||||||
|
return item.name;
|
||||||
|
}}
|
||||||
|
.value=${(item: NotificationWebhookMapping | undefined): string | undefined => {
|
||||||
|
return item?.pk;
|
||||||
|
}}
|
||||||
|
.selected=${(item: NotificationWebhookMapping): boolean => {
|
||||||
|
return this.instance?.webhookMappingHeaders === item.pk;
|
||||||
|
}}
|
||||||
|
blankable
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
@ -7,11 +7,10 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
|||||||
import "@goauthentik/components/ak-status-label";
|
import "@goauthentik/components/ak-status-label";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/SyncStatusCard";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
||||||
|
@ -6,7 +6,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
@ -8,8 +8,8 @@ import MDProviderOAuth2 from "@goauthentik/docs/add-secure-apps/providers/oauth2
|
|||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
|
import "@goauthentik/elements/ak-mdx";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
>
|
>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="pf-c-description-list__description">
|
<dd class="pf-c-description-list__description">
|
||||||
<div class="pf-c-description-list__text">
|
<div class="pf-c-description-list__text pf-m-monospace">
|
||||||
${this.provider.clientId}
|
${this.provider.clientId}
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
@ -236,7 +236,9 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
<div class="pf-c-description-list__text">
|
<div class="pf-c-description-list__text">
|
||||||
<ul>
|
<ul>
|
||||||
${this.provider.redirectUris.map((ru) => {
|
${this.provider.redirectUris.map((ru) => {
|
||||||
return html`<li>${ru.matchingMode}: ${ru.url}</li>`;
|
return html`<li class="pf-m-monospace">
|
||||||
|
${ru.matchingMode}: ${ru.url}
|
||||||
|
</li>`;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -355,21 +357,20 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
|
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
|
||||||
>
|
>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-markdown
|
<ak-mdx
|
||||||
|
.url=${MDProviderOAuth2}
|
||||||
.replacers=${[
|
.replacers=${[
|
||||||
(input: string) => {
|
(input: string) => {
|
||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return input.replaceAll(
|
return input.replaceAll(
|
||||||
"<application slug>",
|
"<application slug>",
|
||||||
this.provider.assignedApplicationSlug,
|
this.provider.assignedApplicationSlug,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
.md=${MDProviderOAuth2}
|
></ak-mdx>
|
||||||
meta="providers/oauth2/index.md"
|
|
||||||
></ak-markdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -16,10 +16,9 @@ import MDTraefikStandalone from "@goauthentik/docs/add-secure-apps/providers/pro
|
|||||||
import MDHeaderAuthentication from "@goauthentik/docs/add-secure-apps/providers/proxy/header_authentication.mdx";
|
import MDHeaderAuthentication from "@goauthentik/docs/add-secure-apps/providers/proxy/header_authentication.mdx";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import { Replacer } from "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
|
import "@goauthentik/elements/ak-mdx";
|
||||||
|
import type { Replacer } from "@goauthentik/elements/ak-mdx";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
|
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
|
||||||
@ -127,37 +126,30 @@ export class ProxyProviderViewPage extends AKElement {
|
|||||||
{
|
{
|
||||||
label: msg("Nginx (Ingress)"),
|
label: msg("Nginx (Ingress)"),
|
||||||
md: MDNginxIngress,
|
md: MDNginxIngress,
|
||||||
meta: "providers/proxy/_nginx_ingress.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Nginx (Proxy Manager)"),
|
label: msg("Nginx (Proxy Manager)"),
|
||||||
md: MDNginxPM,
|
md: MDNginxPM,
|
||||||
meta: "providers/proxy/_nginx_proxy_manager.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Nginx (standalone)"),
|
label: msg("Nginx (standalone)"),
|
||||||
md: MDNginxStandalone,
|
md: MDNginxStandalone,
|
||||||
meta: "providers/proxy/_nginx_standalone.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Traefik (Ingress)"),
|
label: msg("Traefik (Ingress)"),
|
||||||
md: MDTraefikIngress,
|
md: MDTraefikIngress,
|
||||||
meta: "providers/proxy/_traefik_ingress.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Traefik (Compose)"),
|
label: msg("Traefik (Compose)"),
|
||||||
md: MDTraefikCompose,
|
md: MDTraefikCompose,
|
||||||
meta: "providers/proxy/_traefik_compose.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Traefik (Standalone)"),
|
label: msg("Traefik (Standalone)"),
|
||||||
md: MDTraefikStandalone,
|
md: MDTraefikStandalone,
|
||||||
meta: "providers/proxy/_traefik_standalone.md",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Caddy (Standalone)"),
|
label: msg("Caddy (Standalone)"),
|
||||||
md: MDCaddyStandalone,
|
md: MDCaddyStandalone,
|
||||||
meta: "providers/proxy/_caddy_standalone.md",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const replacers: Replacer[] = [
|
const replacers: Replacer[] = [
|
||||||
@ -195,11 +187,7 @@ export class ProxyProviderViewPage extends AKElement {
|
|||||||
data-tab-title="${server.label}"
|
data-tab-title="${server.label}"
|
||||||
class="pf-c-page__main-section pf-m-no-padding-mobile ak-markdown-section"
|
class="pf-c-page__main-section pf-m-no-padding-mobile ak-markdown-section"
|
||||||
>
|
>
|
||||||
<ak-markdown
|
<ak-mdx .url=${server.md} .replacers=${replacers}></ak-mdx>
|
||||||
.replacers=${replacers}
|
|
||||||
.md=${server.md}
|
|
||||||
meta=${server.meta}
|
|
||||||
></ak-markdown>
|
|
||||||
</section>`;
|
</section>`;
|
||||||
})}</ak-tabs
|
})}</ak-tabs
|
||||||
>`;
|
>`;
|
||||||
@ -265,10 +253,7 @@ export class ProxyProviderViewPage extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-markdown
|
<ak-mdx .url=${MDHeaderAuthentication}></ak-mdx>
|
||||||
.md=${MDHeaderAuthentication}
|
|
||||||
meta="proxy/header_authentication.md"
|
|
||||||
></ak-markdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -245,6 +245,41 @@ export function renderForm(
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("AuthnContextClassRef Property Mapping")}
|
||||||
|
name="authnContextClassRefMapping"
|
||||||
|
>
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (query?: string): Promise<SAMLPropertyMapping[]> => {
|
||||||
|
const args: PropertymappingsProviderSamlListRequest = {
|
||||||
|
ordering: "saml_name",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const items = await new PropertymappingsApi(
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
).propertymappingsProviderSamlList(args);
|
||||||
|
return items.results;
|
||||||
|
}}
|
||||||
|
.renderElement=${(item: SAMLPropertyMapping): string => {
|
||||||
|
return item.name;
|
||||||
|
}}
|
||||||
|
.value=${(item: SAMLPropertyMapping | undefined): string | undefined => {
|
||||||
|
return item?.pk;
|
||||||
|
}}
|
||||||
|
.selected=${(item: SAMLPropertyMapping): boolean => {
|
||||||
|
return provider?.authnContextClassRefMapping === item.pk;
|
||||||
|
}}
|
||||||
|
?blankable=${true}
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Configure how the AuthnContextClassRef value will be created. When left empty, the AuthnContextClassRef will be set based on which authentication methods the user used to authenticate.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="assertionValidNotBefore"
|
name="assertionValidNotBefore"
|
||||||
|
@ -24,6 +24,7 @@ export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandable = true;
|
||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
@ -81,6 +82,13 @@ export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
|
|||||||
html`${item.id}`,
|
html`${item.id}`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
renderExpanded(item: SCIMProviderGroup): TemplateResult {
|
||||||
|
return html`<td role="cell" colspan="4">
|
||||||
|
<div class="pf-c-table__expandable-row-content">
|
||||||
|
<pre>${JSON.stringify(item.attributes, null, 4)}</pre>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -24,6 +24,7 @@ export class SCIMProviderUserList extends Table<SCIMProviderUser> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandable = true;
|
||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
@ -82,6 +83,13 @@ export class SCIMProviderUserList extends Table<SCIMProviderUser> {
|
|||||||
html`${item.id}`,
|
html`${item.id}`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
renderExpanded(item: SCIMProviderUser): TemplateResult {
|
||||||
|
return html`<td role="cell" colspan="4">
|
||||||
|
<div class="pf-c-table__expandable-row-content">
|
||||||
|
<pre>${JSON.stringify(item.attributes, null, 4)}</pre>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -9,11 +9,11 @@ import "@goauthentik/components/ak-status-label";
|
|||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
import MDSCIMProvider from "@goauthentik/docs/add-secure-apps/providers/scim/index.md";
|
import MDSCIMProvider from "@goauthentik/docs/add-secure-apps/providers/scim/index.md";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/SyncStatusCard";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
|
import "@goauthentik/elements/ak-mdx";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
import { CSSResult, PropertyValues, TemplateResult, html } from "lit";
|
||||||
@ -243,10 +243,7 @@ export class SCIMProviderViewPage extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card pf-l-grid__item pf-m-5-col">
|
<div class="pf-c-card pf-l-grid__item pf-m-5-col">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-markdown
|
<ak-mdx .url=${MDSCIMProvider}></ak-mdx>
|
||||||
.md=${MDSCIMProvider}
|
|
||||||
meta="providers/scim/index.md"
|
|
||||||
></ak-markdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -62,7 +62,11 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
|
|||||||
<ak-form-group expanded>
|
<ak-form-group expanded>
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||||
<div slot="body" class="pf-c-form">
|
<div slot="body" class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Signing Key")}
|
||||||
|
name="signingKey"
|
||||||
|
required
|
||||||
|
>
|
||||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
certificate=${ifDefined(provider?.signingKey ?? undefined)}
|
certificate=${ifDefined(provider?.signingKey ?? undefined)}
|
||||||
|
@ -7,7 +7,6 @@ import "@goauthentik/components/events/ObjectChangelog";
|
|||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
@ -137,10 +136,13 @@ export class SSFProviderViewPage extends AKElement {
|
|||||||
<dd class="pf-c-description-list__description">
|
<dd class="pf-c-description-list__description">
|
||||||
<div class="pf-c-description-list__text">
|
<div class="pf-c-description-list__text">
|
||||||
<input
|
<input
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control pf-m-monospace"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
value=${this.provider.ssfUrl || ""}
|
value=${this.provider.ssfUrl || ""}
|
||||||
|
placeholder=${this.provider.ssfUrl
|
||||||
|
? msg("SSF URL")
|
||||||
|
: msg("No assigned application")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -7,12 +7,12 @@ import "@goauthentik/components/events/ObjectChangelog";
|
|||||||
import MDSourceKerberosBrowser from "@goauthentik/docs/users-sources/sources/protocols/kerberos/browser.md";
|
import MDSourceKerberosBrowser from "@goauthentik/docs/users-sources/sources/protocols/kerberos/browser.md";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/Markdown";
|
|
||||||
import "@goauthentik/elements/SyncStatusCard";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
|
import "@goauthentik/elements/ak-mdx";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/ModalForm";
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
import { CSSResult, TemplateResult, html } from "lit";
|
||||||
@ -186,11 +186,7 @@ export class KerberosSourceViewPage extends AKElement {
|
|||||||
${this.renderSyncCards()}
|
${this.renderSyncCards()}
|
||||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-markdown
|
<ak-mdx .url=${MDSourceKerberosBrowser}></ak-mdx>
|
||||||
.md=${MDSourceKerberosBrowser}
|
|
||||||
meta="users-sources/protocols/kerberos/browser.md"
|
|
||||||
;
|
|
||||||
></ak-markdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,11 +6,11 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
|||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/SyncStatusCard";
|
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/ModalForm";
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncStatusCard";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
import { CSSResult, TemplateResult, html } from "lit";
|
||||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
|||||||
export const ERROR_CLASS = "pf-m-danger";
|
export const ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
export const CURRENT_CLASS = "pf-m-current";
|
||||||
export const VERSION = "2025.2.1";
|
export const VERSION = "2025.2.2";
|
||||||
export const TITLE_DEFAULT = "authentik";
|
export const TITLE_DEFAULT = "authentik";
|
||||||
export const ROUTE_SEPARATOR = ";";
|
export const ROUTE_SEPARATOR = ";";
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
|
|||||||
|
|
||||||
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
export async function configureSentry(canDoPpi = false): Promise<Config> {
|
||||||
const cfg = await config();
|
const cfg = await config();
|
||||||
|
|
||||||
if (cfg.errorReporting.enabled) {
|
if (cfg.errorReporting.enabled) {
|
||||||
init({
|
init({
|
||||||
dsn: cfg.errorReporting.sentryDsn,
|
dsn: cfg.errorReporting.sentryDsn,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user