Compare commits
1 Commits
typescript
...
permission
| Author | SHA1 | Date | |
|---|---|---|---|
| ff787a0f59 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.2.2
|
||||
current_version = 2025.2.1
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
||||
@ -10,9 +10,6 @@ insert_final_newline = true
|
||||
[*.html]
|
||||
indent_size = 2
|
||||
|
||||
[schemas/*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
16
.github/actions/setup/action.yml
vendored
16
.github/actions/setup/action.yml
vendored
@ -9,22 +9,17 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install apt deps
|
||||
- name: Install poetry & deps
|
||||
shell: bash
|
||||
run: |
|
||||
pipx install poetry || true
|
||||
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
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Setup python
|
||||
- name: Setup python and restore poetry
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: "pyproject.toml"
|
||||
- name: Install Python deps
|
||||
shell: bash
|
||||
run: uv sync --all-extras --dev --frozen
|
||||
cache: "poetry"
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@ -44,9 +39,10 @@ runs:
|
||||
run: |
|
||||
export PSQL_TAG=${{ inputs.postgresql_version }}
|
||||
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
||||
poetry sync
|
||||
cd web && npm ci
|
||||
- name: Generate config
|
||||
shell: uv run python {0}
|
||||
shell: poetry run python {0}
|
||||
run: |
|
||||
from authentik.lib.generators import generate_id
|
||||
from yaml import safe_dump
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -98,7 +98,7 @@ updates:
|
||||
prefix: "lifecycle/aws:"
|
||||
labels:
|
||||
- dependencies
|
||||
- package-ecosystem: uv
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
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
|
||||
- name: Check changes have been applied
|
||||
run: |
|
||||
uv run make aws-cfn
|
||||
poetry run make aws-cfn
|
||||
git diff --exit-code
|
||||
ci-aws-cfn-mark:
|
||||
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
|
||||
uses: ./.github/actions/setup
|
||||
- name: run job
|
||||
run: uv run make ci-${{ matrix.job }}
|
||||
run: poetry run make ci-${{ matrix.job }}
|
||||
test-migrations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -42,7 +42,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: run migrations
|
||||
run: uv run python -m lifecycle.migrate
|
||||
run: poetry run python -m lifecycle.migrate
|
||||
test-make-seed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -69,21 +69,19 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: checkout stable
|
||||
run: |
|
||||
# Delete all poetry envs
|
||||
rm -rf /home/runner/.cache/pypoetry
|
||||
# Copy current, latest config to local
|
||||
# Temporarly comment the .github backup while migrating to uv
|
||||
cp authentik/lib/default.yml local.env.yml
|
||||
# cp -R .github ..
|
||||
cp -R .github ..
|
||||
cp -R scripts ..
|
||||
git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
|
||||
# rm -rf .github/ scripts/
|
||||
# mv ../.github ../scripts .
|
||||
rm -rf scripts/
|
||||
mv ../scripts .
|
||||
rm -rf .github/ scripts/
|
||||
mv ../.github ../scripts .
|
||||
- name: Setup authentik env (stable)
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
continue-on-error: true
|
||||
- name: run migrations to stable
|
||||
run: poetry run python -m lifecycle.migrate
|
||||
- name: checkout current code
|
||||
@ -93,13 +91,15 @@ jobs:
|
||||
git reset --hard HEAD
|
||||
git clean -d -fx .
|
||||
git checkout $GITHUB_SHA
|
||||
# Delete previous poetry env
|
||||
rm -rf /home/runner/.cache/pypoetry/virtualenvs/*
|
||||
- name: Setup authentik env (ensure latest deps are installed)
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
postgresql_version: ${{ matrix.psql }}
|
||||
- name: migrate to latest
|
||||
run: |
|
||||
uv run python -m lifecycle.migrate
|
||||
poetry run python -m lifecycle.migrate
|
||||
- name: run tests
|
||||
env:
|
||||
# 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_TOTAL_RUNS: "5"
|
||||
run: |
|
||||
uv run make ci-test
|
||||
poetry run make ci-test
|
||||
test-unittest:
|
||||
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
|
||||
runs-on: ubuntu-latest
|
||||
@ -133,7 +133,7 @@ jobs:
|
||||
CI_RUN_ID: ${{ matrix.run_id }}
|
||||
CI_TOTAL_RUNS: "5"
|
||||
run: |
|
||||
uv run make ci-test
|
||||
poetry run make ci-test
|
||||
- if: ${{ always() }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
@ -156,8 +156,8 @@ jobs:
|
||||
uses: helm/kind-action@v1.12.0
|
||||
- name: run integration
|
||||
run: |
|
||||
uv run coverage run manage.py test tests/integration
|
||||
uv run coverage xml
|
||||
poetry run coverage run manage.py test tests/integration
|
||||
poetry run coverage xml
|
||||
- if: ${{ always() }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
@ -214,8 +214,8 @@ jobs:
|
||||
npm run build
|
||||
- name: run e2e
|
||||
run: |
|
||||
uv run coverage run manage.py test ${{ matrix.job.glob }}
|
||||
uv run coverage xml
|
||||
poetry run coverage run manage.py test ${{ matrix.job.glob }}
|
||||
poetry run coverage xml
|
||||
- if: ${{ always() }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
|
||||
@ -2,7 +2,7 @@ name: authentik-gen-update-webauthn-mds
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "30 1 1,15 * *"
|
||||
- cron: '30 1 1,15 * *'
|
||||
|
||||
env:
|
||||
POSTGRES_DB: authentik
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- run: uv run ak update_webauthn_mds
|
||||
- run: poetry run ak update_webauthn_mds
|
||||
- uses: peter-evans/create-pull-request@v7
|
||||
id: cpr
|
||||
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
|
||||
- name: generate docs
|
||||
run: |
|
||||
uv run make migrate
|
||||
uv run ak build_source_docs
|
||||
poetry run make migrate
|
||||
poetry run ak build_source_docs
|
||||
- name: Publish
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
|
||||
27
.github/workflows/semgrep.yml
vendored
27
.github/workflows/semgrep.yml
vendored
@ -1,27 +0,0 @@
|
||||
name: authentik-semgrep
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
pull_request: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/semgrep.yml
|
||||
schedule:
|
||||
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
||||
- cron: '12 15 * * *'
|
||||
jobs:
|
||||
semgrep:
|
||||
name: semgrep/ci
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
if: (github.actor != 'dependabot[bot]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: semgrep ci
|
||||
@ -36,10 +36,10 @@ jobs:
|
||||
run: make gen-client-ts
|
||||
- name: run extract
|
||||
run: |
|
||||
uv run make i18n-extract
|
||||
poetry run make i18n-extract
|
||||
- name: run compile
|
||||
run: |
|
||||
uv run ak compilemessages
|
||||
poetry run ak compilemessages
|
||||
make web-check-compile
|
||||
- name: Create Pull Request
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@ -33,7 +33,6 @@ eggs/
|
||||
lib64/
|
||||
parts/
|
||||
dist/
|
||||
out/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
@ -213,25 +212,3 @@ source_docs/
|
||||
|
||||
### Docker ###
|
||||
docker-compose.override.yml
|
||||
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules/
|
||||
|
||||
|
||||
# Wireit's cache
|
||||
.wireit
|
||||
|
||||
custom-elements.json
|
||||
|
||||
|
||||
### Development ###
|
||||
.drafts
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
# Prettier Ignorefile
|
||||
|
||||
## Static Files
|
||||
**/LICENSE
|
||||
|
||||
authentik/stages/**/*
|
||||
|
||||
## Build asset directories
|
||||
coverage
|
||||
dist
|
||||
out
|
||||
.docusaurus
|
||||
website/docs/developer-docs/api/**/*
|
||||
|
||||
## Environment
|
||||
*.env
|
||||
|
||||
## Secrets
|
||||
*.secrets
|
||||
|
||||
## Yarn
|
||||
.yarn/**/*
|
||||
|
||||
## Node
|
||||
node_modules
|
||||
coverage
|
||||
|
||||
## Configs
|
||||
*.log
|
||||
*.yaml
|
||||
*.yml
|
||||
|
||||
# Templates
|
||||
# TODO: Rename affected files to *.template.* or similar.
|
||||
*.html
|
||||
*.mdx
|
||||
*.md
|
||||
|
||||
## Import order matters
|
||||
poly.ts
|
||||
src/locale-codes.ts
|
||||
src/locales/
|
||||
|
||||
# Storybook
|
||||
storybook-static/
|
||||
.storybook/css-import-maps*
|
||||
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -17,6 +17,6 @@
|
||||
"ms-python.vscode-pylance",
|
||||
"redhat.vscode-yaml",
|
||||
"Tobermory.es6-string-html",
|
||||
"unifiedjs.vscode-mdx"
|
||||
"unifiedjs.vscode-mdx",
|
||||
]
|
||||
}
|
||||
|
||||
57
.vscode/settings.json
vendored
57
.vscode/settings.json
vendored
@ -16,7 +16,7 @@
|
||||
],
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"typescript.tsdk": "./web/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"yaml.schemas": {
|
||||
"./blueprints/schema.json": "blueprints/**/*.yaml"
|
||||
@ -30,56 +30,7 @@
|
||||
}
|
||||
],
|
||||
"go.testFlags": ["-count=1"],
|
||||
"github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"],
|
||||
|
||||
"eslint.useFlatConfig": true,
|
||||
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.cjs": "*.d.cts",
|
||||
"package.json": "package-lock.json, yarn.lock, .yarnrc, .yarnrc.yml, .yarn, .nvmrc, .node-version",
|
||||
"tsconfig.json": "tsconfig.*.json, jsconfig.json"
|
||||
},
|
||||
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/*.code-search": true,
|
||||
"**/dist": true,
|
||||
"**/out": true,
|
||||
"**/package-lock.json": true
|
||||
},
|
||||
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[shellscript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.removeUnusedImports": "explicit"
|
||||
},
|
||||
// We use Prettier for formatting, but specifying these settings
|
||||
// will ensure that VS Code's IntelliSense doesn't autocomplete unformatted code.
|
||||
"javascript.format.semicolons": "insert",
|
||||
"typescript.format.semicolons": "insert",
|
||||
"javascript.preferences.quoteStyle": "double",
|
||||
"typescript.preferences.quoteStyle": "double"
|
||||
"github-actions.workflows.pinned.workflows": [
|
||||
".github/workflows/ci-main.yml"
|
||||
]
|
||||
}
|
||||
|
||||
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@ -3,7 +3,7 @@
|
||||
"tasks": [
|
||||
{
|
||||
"label": "authentik/core: make",
|
||||
"command": "uv",
|
||||
"command": "poetry",
|
||||
"args": ["run", "make", "lint-fix", "lint"],
|
||||
"presentation": {
|
||||
"panel": "new"
|
||||
@ -12,7 +12,7 @@
|
||||
},
|
||||
{
|
||||
"label": "authentik/core: run",
|
||||
"command": "uv",
|
||||
"command": "poetry",
|
||||
"args": ["run", "ak", "server"],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
@ -60,7 +60,7 @@
|
||||
},
|
||||
{
|
||||
"label": "authentik/api: generate",
|
||||
"command": "uv",
|
||||
"command": "poetry",
|
||||
"args": ["run", "make", "gen"],
|
||||
"group": "build"
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ schemas/ @goauthentik/backend
|
||||
scripts/ @goauthentik/backend
|
||||
tests/ @goauthentik/backend
|
||||
pyproject.toml @goauthentik/backend
|
||||
uv.lock @goauthentik/backend
|
||||
poetry.lock @goauthentik/backend
|
||||
go.mod @goauthentik/backend
|
||||
go.sum @goauthentik/backend
|
||||
# Infrastructure
|
||||
|
||||
88
Dockerfile
88
Dockerfile
@ -3,7 +3,8 @@
|
||||
# Stage 1: Build website
|
||||
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
|
||||
|
||||
@ -93,59 +94,53 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
mkdir -p /usr/share/GeoIP && \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Download uv
|
||||
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
|
||||
# Stage 5: Python dependencies
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS python-deps
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
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
|
||||
WORKDIR /ak-root/poetry
|
||||
|
||||
ENV PATH="/root/.cargo/bin:$PATH"
|
||||
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 --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
|
||||
apt-get update && \
|
||||
# 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 \
|
||||
# Build essentials
|
||||
build-essential pkg-config libffi-dev git \
|
||||
# cryptography
|
||||
curl \
|
||||
# libxml
|
||||
libxslt-dev zlib1g-dev \
|
||||
# postgresql
|
||||
libpq-dev \
|
||||
# python-kadmin-rs
|
||||
clang libkrb5-dev sccache \
|
||||
# xmlsec
|
||||
libltdl-dev && \
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
build-essential libffi-dev \
|
||||
# Required for cryptography
|
||||
curl pkg-config \
|
||||
# Required for lxml
|
||||
libxslt-dev zlib1g-dev \
|
||||
# Required for xmlsec
|
||||
libltdl-dev \
|
||||
# Required for kadmin
|
||||
sccache clang && \
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
. "$HOME/.cargo/env" && \
|
||||
python -m venv /ak-root/venv/ && \
|
||||
bash -c "source ${VENV_PATH}/bin/activate && \
|
||||
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"
|
||||
|
||||
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec"
|
||||
|
||||
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
|
||||
# Stage 6: Run
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS final-image
|
||||
|
||||
ARG VERSION
|
||||
ARG GIT_BUILD_HASH
|
||||
@ -177,7 +172,7 @@ RUN apt-get update && \
|
||||
|
||||
COPY ./authentik/ /authentik
|
||||
COPY ./pyproject.toml /
|
||||
COPY ./uv.lock /
|
||||
COPY ./poetry.lock /
|
||||
COPY ./schemas /schemas
|
||||
COPY ./locale /locale
|
||||
COPY ./tests /tests
|
||||
@ -186,7 +181,7 @@ COPY ./blueprints /blueprints
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
|
||||
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/authentik/ /web/authentik/
|
||||
COPY --from=website-builder /work/website/build/ /website/help/
|
||||
@ -197,6 +192,9 @@ USER 1000
|
||||
ENV TMPDIR=/dev/shm/ \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/ak-root/venv/bin:/lifecycle:$PATH" \
|
||||
VENV_PATH="/ak-root/venv" \
|
||||
POETRY_VIRTUALENVS_CREATE=false \
|
||||
GOFIPS=1
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
||||
|
||||
63
Makefile
63
Makefile
@ -12,9 +12,9 @@ GEN_API_TS = "gen-ts-api"
|
||||
GEN_API_PY = "gen-py-api"
|
||||
GEN_API_GO = "gen-go-api"
|
||||
|
||||
pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null)
|
||||
pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null)
|
||||
pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null)
|
||||
pg_user := $(shell poetry 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_name := $(shell poetry run python -m authentik.lib.config postgresql.name 2>/dev/null)
|
||||
|
||||
all: lint-fix lint test gen web ## Lint, build, and test everything
|
||||
|
||||
@ -32,37 +32,34 @@ go-test:
|
||||
go test -timeout 0 -v -race -cover ./...
|
||||
|
||||
test: ## Run the server tests and produce a coverage report (locally)
|
||||
uv run coverage run manage.py test --keepdb authentik
|
||||
uv run coverage html
|
||||
uv run coverage report
|
||||
poetry run coverage run manage.py test --keepdb authentik
|
||||
poetry run coverage html
|
||||
poetry run coverage report
|
||||
|
||||
lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
|
||||
uv run black $(PY_SOURCES)
|
||||
uv run ruff check --fix $(PY_SOURCES)
|
||||
poetry run black $(PY_SOURCES)
|
||||
poetry run ruff check --fix $(PY_SOURCES)
|
||||
|
||||
lint-codespell: ## Reports spelling errors.
|
||||
uv run codespell -w
|
||||
poetry run codespell -w
|
||||
|
||||
lint: ## Lint the python and golang sources
|
||||
uv run bandit -c pyproject.toml -r $(PY_SOURCES)
|
||||
poetry run bandit -c pyproject.toml -r $(PY_SOURCES)
|
||||
golangci-lint run -v
|
||||
|
||||
core-install:
|
||||
uv sync --frozen
|
||||
poetry install
|
||||
|
||||
migrate: ## Run the Authentik Django server's migrations
|
||||
uv run python -m lifecycle.migrate
|
||||
poetry 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
|
||||
|
||||
aws-cfn:
|
||||
cd lifecycle/aws && npm run aws-cfn
|
||||
|
||||
run: ## Run the main authentik server process
|
||||
uv run ak server
|
||||
|
||||
core-i18n-extract:
|
||||
uv run ak makemessages \
|
||||
poetry run ak makemessages \
|
||||
--add-location file \
|
||||
--no-obsolete \
|
||||
--ignore web \
|
||||
@ -93,11 +90,11 @@ gen-build: ## Extract the schema from the database
|
||||
AUTHENTIK_DEBUG=true \
|
||||
AUTHENTIK_TENANTS__ENABLED=true \
|
||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||
uv run ak make_blueprint_schema > blueprints/schema.json
|
||||
poetry run ak make_blueprint_schema > blueprints/schema.json
|
||||
AUTHENTIK_DEBUG=true \
|
||||
AUTHENTIK_TENANTS__ENABLED=true \
|
||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||
uv run ak spectacular --file schema.yml
|
||||
poetry run ak spectacular --file schema.yml
|
||||
|
||||
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
|
||||
@ -133,14 +130,16 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
|
||||
--input-spec /local/schema.yml \
|
||||
--generator-name typescript-fetch \
|
||||
--output /local/${GEN_API_TS} \
|
||||
--config /local/scripts/api-ts-config.yaml \
|
||||
-i /local/schema.yml \
|
||||
-g typescript-fetch \
|
||||
-o /local/${GEN_API_TS} \
|
||||
-c /local/scripts/api-ts-config.yaml \
|
||||
--additional-properties=npmVersion=${NPM_VERSION} \
|
||||
--git-repo-id authentik \
|
||||
--git-user-id goauthentik
|
||||
npm install
|
||||
mkdir -p web/node_modules/@goauthentik/api
|
||||
cd ./${GEN_API_TS} && npm i
|
||||
\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api
|
||||
|
||||
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
|
||||
docker run \
|
||||
@ -174,7 +173,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/
|
||||
|
||||
gen-dev-config: ## Generate a local development config file
|
||||
uv run scripts/generate_config.py
|
||||
poetry run scripts/generate_config.py
|
||||
|
||||
gen: gen-build gen-client-ts
|
||||
|
||||
@ -255,21 +254,21 @@ ci--meta-debug:
|
||||
node --version
|
||||
|
||||
ci-black: ci--meta-debug
|
||||
uv run black --check $(PY_SOURCES)
|
||||
poetry run black --check $(PY_SOURCES)
|
||||
|
||||
ci-ruff: ci--meta-debug
|
||||
uv run ruff check $(PY_SOURCES)
|
||||
poetry run ruff check $(PY_SOURCES)
|
||||
|
||||
ci-codespell: ci--meta-debug
|
||||
uv run codespell -s
|
||||
poetry run codespell -s
|
||||
|
||||
ci-bandit: ci--meta-debug
|
||||
uv run bandit -r $(PY_SOURCES)
|
||||
poetry run bandit -r $(PY_SOURCES)
|
||||
|
||||
ci-pending-migrations: ci--meta-debug
|
||||
uv run ak makemigrations --check
|
||||
poetry run ak makemigrations --check
|
||||
|
||||
ci-test: ci--meta-debug
|
||||
uv run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
|
||||
uv run coverage report
|
||||
uv run coverage xml
|
||||
poetry run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
|
||||
poetry run coverage report
|
||||
poetry run coverage xml
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.2.2"
|
||||
__version__ = "2025.2.1"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ class SystemInfoSerializer(PassiveSerializer):
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
actual_value = value
|
||||
if raw_session is not None and raw_session in actual_value:
|
||||
if raw_session in actual_value:
|
||||
actual_value = actual_value.replace(
|
||||
raw_session, SafeExceptionReporterFilter.cleansed_substitute
|
||||
)
|
||||
|
||||
@ -49,8 +49,6 @@ class BrandSerializer(ModelSerializer):
|
||||
"branding_title",
|
||||
"branding_logo",
|
||||
"branding_favicon",
|
||||
"branding_custom_css",
|
||||
"branding_default_flow_background",
|
||||
"flow_authentication",
|
||||
"flow_invalidation",
|
||||
"flow_recovery",
|
||||
@ -88,7 +86,6 @@ class CurrentBrandSerializer(PassiveSerializer):
|
||||
branding_title = CharField()
|
||||
branding_logo = CharField(source="branding_logo_url")
|
||||
branding_favicon = CharField(source="branding_favicon_url")
|
||||
branding_custom_css = CharField()
|
||||
ui_footer_links = ListField(
|
||||
child=FooterLinkSerializer(),
|
||||
read_only=True,
|
||||
@ -128,7 +125,6 @@ class BrandViewSet(UsedByMixin, ModelViewSet):
|
||||
"branding_title",
|
||||
"branding_logo",
|
||||
"branding_favicon",
|
||||
"branding_default_flow_background",
|
||||
"flow_authentication",
|
||||
"flow_invalidation",
|
||||
"flow_recovery",
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
# 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
|
||||
css = path.read_text()
|
||||
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),
|
||||
]
|
||||
@ -1,18 +0,0 @@
|
||||
# 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,10 +33,6 @@ class Brand(SerializerModel):
|
||||
|
||||
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_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, null=True, on_delete=models.SET_NULL, related_name="brand_authentication"
|
||||
@ -88,12 +84,6 @@ class Brand(SerializerModel):
|
||||
return CONFIG.get("web.path", "/")[:-1] + 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
|
||||
def serializer(self) -> Serializer:
|
||||
from authentik.brands.api import BrandSerializer
|
||||
|
||||
@ -24,7 +24,6 @@ class TestBrands(APITestCase):
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||
"branding_title": "authentik",
|
||||
"branding_custom_css": "",
|
||||
"matched_domain": brand.domain,
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
@ -44,7 +43,6 @@ class TestBrands(APITestCase):
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||
"branding_title": "custom",
|
||||
"branding_custom_css": "",
|
||||
"matched_domain": "bar.baz",
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
@ -61,7 +59,6 @@ class TestBrands(APITestCase):
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||
"branding_title": "authentik",
|
||||
"branding_custom_css": "",
|
||||
"matched_domain": "fallback",
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
@ -124,27 +121,3 @@ class TestBrands(APITestCase):
|
||||
"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 %}
|
||||
{% endblock %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
<style>{{ brand.branding_custom_css }}</style>
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
|
||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_before %}
|
||||
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
|
||||
<link rel="prefetch" href="{% static 'dist/assets/images/flow_background.jpg' %}" />
|
||||
<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)">
|
||||
{% include "base/header_js.html" %}
|
||||
@ -13,7 +13,7 @@
|
||||
{% block head %}
|
||||
<style>
|
||||
:root {
|
||||
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
|
||||
--ak-flow-background: url("{% static 'dist/assets/images/flow_background.jpg' %}");
|
||||
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
|
||||
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
|
||||
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
|
||||
|
||||
@ -50,8 +50,7 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||
"mode",
|
||||
"mode_verbose",
|
||||
"webhook_url",
|
||||
"webhook_mapping_body",
|
||||
"webhook_mapping_headers",
|
||||
"webhook_mapping",
|
||||
"send_once",
|
||||
]
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
# 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,27 +336,8 @@ class NotificationTransport(SerializerModel):
|
||||
mode = models.TextField(choices=TransportMode.choices, default=TransportMode.LOCAL)
|
||||
|
||||
webhook_url = models.TextField(blank=True, validators=[DomainlessURLValidator()])
|
||||
webhook_mapping_body = models.ForeignKey(
|
||||
"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"
|
||||
),
|
||||
webhook_mapping = models.ForeignKey(
|
||||
"NotificationWebhookMapping", on_delete=models.SET_DEFAULT, null=True, default=None
|
||||
)
|
||||
send_once = models.BooleanField(
|
||||
default=False,
|
||||
@ -379,8 +360,8 @@ class NotificationTransport(SerializerModel):
|
||||
|
||||
def send_local(self, notification: "Notification") -> list[str]:
|
||||
"""Local notification delivery"""
|
||||
if self.webhook_mapping_body:
|
||||
self.webhook_mapping_body.evaluate(
|
||||
if self.webhook_mapping:
|
||||
self.webhook_mapping.evaluate(
|
||||
user=notification.user,
|
||||
request=None,
|
||||
notification=notification,
|
||||
@ -399,18 +380,9 @@ class NotificationTransport(SerializerModel):
|
||||
if notification.event and notification.event.user:
|
||||
default_body["event_user_email"] = notification.event.user.get("email", None)
|
||||
default_body["event_user_username"] = notification.event.user.get("username", None)
|
||||
headers = {}
|
||||
if self.webhook_mapping_body:
|
||||
if self.webhook_mapping:
|
||||
default_body = sanitize_item(
|
||||
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(
|
||||
self.webhook_mapping.evaluate(
|
||||
user=notification.user,
|
||||
request=None,
|
||||
notification=notification,
|
||||
@ -420,7 +392,6 @@ class NotificationTransport(SerializerModel):
|
||||
response = get_http_session().post(
|
||||
self.webhook_url,
|
||||
json=default_body,
|
||||
headers=headers,
|
||||
)
|
||||
response.raise_for_status()
|
||||
except RequestException as exc:
|
||||
|
||||
@ -120,7 +120,7 @@ class TestEventsNotifications(APITestCase):
|
||||
)
|
||||
|
||||
transport = NotificationTransport.objects.create(
|
||||
name=generate_id(), webhook_mapping_body=mapping, mode=TransportMode.LOCAL
|
||||
name=generate_id(), webhook_mapping=mapping, mode=TransportMode.LOCAL
|
||||
)
|
||||
NotificationRule.objects.filter(name__startswith="default").delete()
|
||||
trigger = NotificationRule.objects.create(name=generate_id(), group=self.group)
|
||||
|
||||
@ -60,25 +60,20 @@ class TestEventTransports(TestCase):
|
||||
|
||||
def test_transport_webhook_mapping(self):
|
||||
"""Test webhook transport with custom mapping"""
|
||||
mapping_body = NotificationWebhookMapping.objects.create(
|
||||
mapping = NotificationWebhookMapping.objects.create(
|
||||
name=generate_id(), expression="return request.user"
|
||||
)
|
||||
mapping_headers = NotificationWebhookMapping.objects.create(
|
||||
name=generate_id(), expression="""return {"foo": "bar"}"""
|
||||
)
|
||||
transport: NotificationTransport = NotificationTransport.objects.create(
|
||||
name=generate_id(),
|
||||
mode=TransportMode.WEBHOOK,
|
||||
webhook_url="http://localhost:1234/test",
|
||||
webhook_mapping_body=mapping_body,
|
||||
webhook_mapping_headers=mapping_headers,
|
||||
webhook_mapping=mapping,
|
||||
)
|
||||
with Mocker() as mocker:
|
||||
mocker.post("http://localhost:1234/test")
|
||||
transport.send(self.notification)
|
||||
self.assertEqual(mocker.call_count, 1)
|
||||
self.assertEqual(mocker.request_history[0].method, "POST")
|
||||
self.assertEqual(mocker.request_history[0].headers["foo"], "bar")
|
||||
self.assertJSONEqual(
|
||||
mocker.request_history[0].body.decode(),
|
||||
{"email": self.user.email, "pk": self.user.pk, "username": self.user.username},
|
||||
|
||||
@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils.managers import InheritanceManager
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
@ -179,12 +178,11 @@ class Flow(SerializerModel, PolicyBindingModel):
|
||||
help_text=_("Required level of authentication and authorization to access a flow."),
|
||||
)
|
||||
|
||||
def background_url(self, request: HttpRequest | None = None) -> str:
|
||||
@property
|
||||
def background_url(self) -> str:
|
||||
"""Get the URL to the background image. If the name is /static or starts with http
|
||||
it is returned as-is"""
|
||||
if not self.background:
|
||||
if request:
|
||||
return request.brand.branding_default_flow_background_url()
|
||||
return (
|
||||
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg"
|
||||
)
|
||||
|
||||
@ -184,7 +184,7 @@ class ChallengeStageView(StageView):
|
||||
flow_info = ContextualFlowInfo(
|
||||
data={
|
||||
"title": self.format_title(),
|
||||
"background": self.executor.flow.background_url(self.request),
|
||||
"background": self.executor.flow.background_url,
|
||||
"cancel_url": reverse("authentik_flows:cancel"),
|
||||
"layout": self.executor.flow.layout,
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ class FlowTestCase(APITestCase):
|
||||
self.assertIsNotNone(raw_response["component"])
|
||||
if flow:
|
||||
self.assertIn("flow_info", raw_response)
|
||||
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
|
||||
self.assertEqual(
|
||||
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
|
||||
)
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
"""API flow tests"""
|
||||
|
||||
from json import loads
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.api.stages import StageSerializer, StageViewSet
|
||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
|
||||
from authentik.lib.generators import generate_id
|
||||
@ -79,22 +77,6 @@ class TestFlowsAPI(APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(response.content, {"diagram": DIAGRAM_EXPECTED})
|
||||
|
||||
def test_api_background(self):
|
||||
"""Test custom background"""
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
flow = create_test_flow()
|
||||
response = self.client.get(reverse("authentik_api:flow-detail", kwargs={"slug": flow.slug}))
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["background"], "/static/dist/assets/images/flow_background.jpg")
|
||||
|
||||
flow.background = "https://goauthentik.io/img/icon.png"
|
||||
flow.save()
|
||||
response = self.client.get(reverse("authentik_api:flow-detail", kwargs={"slug": flow.slug}))
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["background"], "https://goauthentik.io/img/icon.png")
|
||||
|
||||
def test_api_diagram_no_stages(self):
|
||||
"""Test flow diagram with no stages."""
|
||||
user = create_test_admin_user()
|
||||
|
||||
@ -49,7 +49,7 @@ class TestFlowInspector(APITestCase):
|
||||
"captcha_stage": None,
|
||||
"component": "ak-stage-identification",
|
||||
"flow_info": {
|
||||
"background": "/static/dist/assets/images/flow_background.jpg",
|
||||
"background": flow.background_url,
|
||||
"cancel_url": reverse("authentik_flows:cancel"),
|
||||
"title": flow.title,
|
||||
"layout": "stacked",
|
||||
|
||||
@ -45,8 +45,6 @@ redis:
|
||||
# url: ""
|
||||
# transport_options: ""
|
||||
|
||||
http_timeout: 30
|
||||
|
||||
cache:
|
||||
# url: ""
|
||||
timeout: 300
|
||||
|
||||
@ -16,40 +16,7 @@ def authentik_user_agent() -> str:
|
||||
return f"authentik@{get_full_version()}"
|
||||
|
||||
|
||||
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):
|
||||
class DebugSession(Session):
|
||||
"""requests session which logs http requests and responses"""
|
||||
|
||||
def send(self, req: PreparedRequest, *args, **kwargs):
|
||||
@ -75,9 +42,8 @@ class DebugSession(TimeoutSession):
|
||||
|
||||
def get_http_session() -> Session:
|
||||
"""Get a requests session with common headers"""
|
||||
session = TimeoutSession()
|
||||
session = Session()
|
||||
if CONFIG.get_bool("debug") or CONFIG.get("log_level") == "trace":
|
||||
session = DebugSession()
|
||||
session.headers["User-Agent"] = authentik_user_agent()
|
||||
session.timeout = CONFIG.get_optional_int("http_timeout")
|
||||
return session
|
||||
|
||||
@ -180,7 +180,6 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"session_valid_not_on_or_after",
|
||||
"property_mappings",
|
||||
"name_id_mapping",
|
||||
"authn_context_class_ref_mapping",
|
||||
"digest_algorithm",
|
||||
"signature_algorithm",
|
||||
"signing_kp",
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
# 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,20 +71,6 @@ class SAMLProvider(Provider):
|
||||
"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(
|
||||
default="minutes=-5",
|
||||
@ -184,6 +170,7 @@ class SAMLProvider(Provider):
|
||||
def launch_url(self) -> str | None:
|
||||
"""Use IDP-Initiated SAML flow as launch URL"""
|
||||
try:
|
||||
|
||||
return reverse(
|
||||
"authentik_providers_saml:sso-init",
|
||||
kwargs={"application_slug": self.application.slug},
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""SAML Assertion generator"""
|
||||
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
from types import GeneratorType
|
||||
|
||||
@ -53,7 +52,6 @@ class AssertionProcessor:
|
||||
_assertion_id: str
|
||||
_response_id: str
|
||||
|
||||
_auth_instant: str
|
||||
_valid_not_before: str
|
||||
_session_not_on_or_after: str
|
||||
_valid_not_on_or_after: str
|
||||
@ -67,11 +65,6 @@ class AssertionProcessor:
|
||||
self._assertion_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(
|
||||
timedelta_from_string(self.provider.assertion_valid_not_before)
|
||||
)
|
||||
@ -138,7 +131,7 @@ class AssertionProcessor:
|
||||
def get_assertion_auth_n_statement(self) -> Element:
|
||||
"""Generate AuthnStatement with AuthnContext and ContextClassRef Elements."""
|
||||
auth_n_statement = Element(f"{{{NS_SAML_ASSERTION}}}AuthnStatement")
|
||||
auth_n_statement.attrib["AuthnInstant"] = self._auth_instant
|
||||
auth_n_statement.attrib["AuthnInstant"] = self._valid_not_before
|
||||
auth_n_statement.attrib["SessionIndex"] = sha256(
|
||||
self.http_request.session.session_key.encode("ascii")
|
||||
).hexdigest()
|
||||
@ -165,28 +158,6 @@ class AssertionProcessor:
|
||||
auth_n_context_class_ref.text = (
|
||||
"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
|
||||
|
||||
def get_assertion_conditions(self) -> Element:
|
||||
|
||||
@ -294,61 +294,6 @@ class TestAuthNRequest(TestCase):
|
||||
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
|
||||
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):
|
||||
"""Test full SAML Request/Response flow, fully signed"""
|
||||
user = create_test_admin_user()
|
||||
@ -376,10 +321,8 @@ class TestAuthNRequest(TestCase):
|
||||
request = request_proc.build_auth_n()
|
||||
|
||||
# Create invalid PropertyMapping
|
||||
mapping = SAMLPropertyMapping.objects.create(
|
||||
name=generate_id(), saml_name="test", expression="q"
|
||||
)
|
||||
self.provider.property_mappings.add(mapping)
|
||||
scope = SAMLPropertyMapping.objects.create(name="test", saml_name="test", expression="q")
|
||||
self.provider.property_mappings.add(scope)
|
||||
|
||||
# To get an assertion we need a parsed request (parsed by provider)
|
||||
parsed_request = AuthNRequestParser(self.provider).parse(
|
||||
@ -395,7 +338,7 @@ class TestAuthNRequest(TestCase):
|
||||
self.assertTrue(events.exists())
|
||||
self.assertEqual(
|
||||
events.first().context["message"],
|
||||
f"Failed to evaluate property-mapping: '{mapping.name}'",
|
||||
"Failed to evaluate property-mapping: 'test'",
|
||||
)
|
||||
|
||||
def test_idp_initiated(self):
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
"""Time utilities"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.utils.timezone import now
|
||||
import datetime
|
||||
|
||||
|
||||
def get_time_string(delta: timedelta | datetime | None = None) -> str:
|
||||
def get_time_string(delta: datetime.timedelta | None = None) -> str:
|
||||
"""Get Data formatted in SAML format"""
|
||||
if delta is None:
|
||||
delta = timedelta()
|
||||
if isinstance(delta, timedelta):
|
||||
final = now() + delta
|
||||
else:
|
||||
final = delta
|
||||
delta = datetime.timedelta()
|
||||
now = datetime.datetime.now()
|
||||
final = now + delta
|
||||
return final.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
@ -24,9 +24,7 @@ class SCIMProviderGroupSerializer(ModelSerializer):
|
||||
"group",
|
||||
"group_obj",
|
||||
"provider",
|
||||
"attributes",
|
||||
]
|
||||
extra_kwargs = {"attributes": {"read_only": True}}
|
||||
|
||||
|
||||
class SCIMProviderGroupViewSet(
|
||||
|
||||
@ -24,9 +24,7 @@ class SCIMProviderUserSerializer(ModelSerializer):
|
||||
"user",
|
||||
"user_obj",
|
||||
"provider",
|
||||
"attributes",
|
||||
]
|
||||
extra_kwargs = {"attributes": {"read_only": True}}
|
||||
|
||||
|
||||
class SCIMProviderUserViewSet(
|
||||
|
||||
@ -102,7 +102,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
if not scim_id or scim_id == "":
|
||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||
connection = SCIMProviderGroup.objects.create(
|
||||
provider=self.provider, group=group, scim_id=scim_id, attributes=response
|
||||
provider=self.provider, group=group, scim_id=scim_id
|
||||
)
|
||||
users = list(group.users.order_by("id").values_list("id", flat=True))
|
||||
self._patch_add_users(connection, users)
|
||||
@ -243,10 +243,9 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
||||
if user.value not in users_should:
|
||||
users_to_remove.append(user.value)
|
||||
# Check users that should be in the group and add them
|
||||
if current_group.members is not None:
|
||||
for user in users_should:
|
||||
if len([x for x in current_group.members if x.value == user]) < 1:
|
||||
users_to_add.append(user)
|
||||
for user in users_should:
|
||||
if len([x for x in current_group.members if x.value == user]) < 1:
|
||||
users_to_add.append(user)
|
||||
# Only send request if we need to make changes
|
||||
if len(users_to_add) < 1 and len(users_to_remove) < 1:
|
||||
return
|
||||
|
||||
@ -77,24 +77,21 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
||||
if len(users_res) < 1:
|
||||
raise exc
|
||||
return SCIMProviderUser.objects.create(
|
||||
provider=self.provider,
|
||||
user=user,
|
||||
scim_id=users_res[0]["id"],
|
||||
attributes=users_res[0],
|
||||
provider=self.provider, user=user, scim_id=users_res[0]["id"]
|
||||
)
|
||||
else:
|
||||
scim_id = response.get("id")
|
||||
if not scim_id or scim_id == "":
|
||||
raise StopSync("SCIM Response with missing or invalid `id`")
|
||||
return SCIMProviderUser.objects.create(
|
||||
provider=self.provider, user=user, scim_id=scim_id, attributes=response
|
||||
provider=self.provider, user=user, scim_id=scim_id
|
||||
)
|
||||
|
||||
def update(self, user: User, connection: SCIMProviderUser):
|
||||
"""Update existing user"""
|
||||
scim_user = self.to_schema(user, connection)
|
||||
scim_user.id = connection.scim_id
|
||||
response = self._request(
|
||||
self._request(
|
||||
"PUT",
|
||||
f"/Users/{connection.scim_id}",
|
||||
json=scim_user.model_dump(
|
||||
@ -102,5 +99,3 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
connection.attributes = response
|
||||
connection.save()
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
# 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,7 +22,6 @@ class SCIMProviderUser(SerializerModel):
|
||||
scim_id = models.TextField()
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
||||
attributes = models.JSONField(default=dict)
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
@ -44,7 +43,6 @@ class SCIMProviderGroup(SerializerModel):
|
||||
scim_id = models.TextField()
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
|
||||
attributes = models.JSONField(default=dict)
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
|
||||
@ -323,7 +323,12 @@
|
||||
"multiValued": false,
|
||||
"description": "Indicates whether or not an attribute is modifiable.",
|
||||
"required": false,
|
||||
"canonicalValues": ["readOnly", "readWrite", "immutable", "writeOnly"],
|
||||
"canonicalValues": [
|
||||
"readOnly",
|
||||
"readWrite",
|
||||
"immutable",
|
||||
"writeOnly"
|
||||
],
|
||||
"caseExact": false,
|
||||
"mutability": "readOnly",
|
||||
"returned": "default",
|
||||
@ -335,7 +340,12 @@
|
||||
"multiValued": false,
|
||||
"description": "Indicates when an attribute is returned in a response (e.g., to a query).",
|
||||
"required": false,
|
||||
"canonicalValues": ["always", "never", "default", "request"],
|
||||
"canonicalValues": [
|
||||
"always",
|
||||
"never",
|
||||
"default",
|
||||
"request"
|
||||
],
|
||||
"caseExact": false,
|
||||
"mutability": "readOnly",
|
||||
"returned": "default",
|
||||
@ -359,7 +369,12 @@
|
||||
"multiValued": true,
|
||||
"description": "Used only with an attribute of type 'reference'. Specifies a SCIM resourceType that a reference attribute MAY refer to, e.g., 'User'.",
|
||||
"required": false,
|
||||
"canonicalValues": ["resource", "external", "uri", "url"],
|
||||
"canonicalValues": [
|
||||
"resource",
|
||||
"external",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"caseExact": false,
|
||||
"mutability": "readOnly",
|
||||
"returned": "default",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -142,35 +142,38 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||
raise ValidationError("Failed to authenticate.")
|
||||
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
|
||||
if captcha_stage := current_stage.captcha_stage:
|
||||
captcha_token = attrs.get("captcha_token", None)
|
||||
if not captcha_token:
|
||||
self.stage.logger.warning("Token not set for captcha attempt")
|
||||
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
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -54,7 +54,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* ESLint configuration for authentik's monorepo.
|
||||
*/
|
||||
const ESLintConfig = createESLintPackageConfig();
|
||||
|
||||
export default ESLintConfig;
|
||||
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-ldap/ldap/v3 v3.4.10
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
@ -23,13 +23,13 @@ require (
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/sethvargo/go-envconfig v1.1.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025022.6
|
||||
goauthentik.io/api/v3 v3.2025021.4
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.28.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/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -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/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
@ -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.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025022.6 h1:M5M8Cd/1N7E8KLkvYYh7VdcdKz5nfzjKPFLK+YOtOVg=
|
||||
goauthentik.io/api/v3 v3.2025022.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025021.4 h1:KFap2KW+8CwhOxjBkRnRB4flvuHEMw24+fZei9dOhzw=
|
||||
goauthentik.io/api/v3 v3.2025021.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
||||
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.2.2"
|
||||
const VERSION = "2025.2.1"
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<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/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" />
|
||||
<style>
|
||||
.pf-c-background-image::before {
|
||||
|
||||
@ -62,12 +62,12 @@ function prepare_debug {
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc
|
||||
VIRTUAL_ENV=/ak-root/.venv uv sync --frozen
|
||||
VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction
|
||||
touch /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
|
||||
fi
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""Wrapper for lifecycle/ak, to be installed by uv"""
|
||||
"""Wrapper for lifecycle/ak, to be installed by poetry"""
|
||||
|
||||
from os import system, waitstatus_to_exitcode
|
||||
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",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1005.0",
|
||||
"aws-cdk": "^2.1004.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1005.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1005.0.tgz",
|
||||
"integrity": "sha512-4ejfGGrGCEl0pg1xcqkxK0lpBEZqNI48wtrXhk6dYOFYPYMZtqn1kdla29ONN+eO2unewkNF4nLP1lPYhlf9Pg==",
|
||||
"version": "2.1004.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1004.0.tgz",
|
||||
"integrity": "sha512-3E5ICmSc7ZCZCwLX7NY+HFmmdUYgRaL+67h/BDoDQmkhx9StC8wG4xgzHFY9k8WQS0+ib/MP28f2d9yzHtQLlQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@goauthentik/lifecycle-aws",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1005.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1004.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.2.2
|
||||
Default: 2025.2.1
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
||||
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -616,18 +616,6 @@ msgstr ""
|
||||
msgid "Email"
|
||||
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
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
@ -1768,17 +1756,6 @@ msgid ""
|
||||
"NameIDPolicy of the incoming request will be considered"
|
||||
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
|
||||
msgid ""
|
||||
"Assertion valid not before current time + this value (Format: hours=-1;"
|
||||
|
||||
@ -19,7 +19,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: Marc Schmitt, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||
@ -676,22 +676,6 @@ msgstr "Webhook Slack (ou Discord)"
|
||||
msgid "Email"
|
||||
msgstr "Courriel"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||
"serializable."
|
||||
msgstr ""
|
||||
"Personnalise le corps de la requête. Le mappage doit renvoyer des données "
|
||||
"sérialisables en JSON."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Configure additional headers to be sent. Mapping should return a dictionary "
|
||||
"of key-value pairs"
|
||||
msgstr ""
|
||||
"Configure les en-têtes supplémentaires à envoyer. Le mappage doit renvoyer "
|
||||
"un dictionnaire de paires clé-valeur."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
@ -1972,20 +1956,6 @@ msgstr ""
|
||||
"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"
|
||||
|
||||
#: 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
|
||||
msgid ""
|
||||
"Assertion valid not before current time + this value (Format: "
|
||||
|
||||
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
|
||||
@ -627,18 +627,6 @@ msgstr "Slack Webhook(Slack/Discord)"
|
||||
msgid "Email"
|
||||
msgstr "电子邮箱"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||
"serializable."
|
||||
msgstr "自定义请求体。映射应该返回 JSON 序列化的数据。"
|
||||
|
||||
#: 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
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
@ -1794,18 +1782,6 @@ msgid ""
|
||||
"NameIDPolicy of the incoming request will be considered"
|
||||
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
|
||||
msgid ""
|
||||
"Assertion valid not before current time + this value (Format: "
|
||||
|
||||
Binary file not shown.
@ -14,7 +14,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: deluxghost, 2025\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||
@ -626,18 +626,6 @@ msgstr "Slack Webhook(Slack/Discord)"
|
||||
msgid "Email"
|
||||
msgstr "电子邮箱"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||
"serializable."
|
||||
msgstr "自定义请求体。映射应该返回 JSON 序列化的数据。"
|
||||
|
||||
#: 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
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
@ -1793,18 +1781,6 @@ msgid ""
|
||||
"NameIDPolicy of the incoming request will be considered"
|
||||
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
|
||||
msgid ""
|
||||
"Assertion valid not before current time + this value (Format: "
|
||||
|
||||
43199
package-lock.json
generated
43199
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@ -1,38 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/universe",
|
||||
"version": "2025.2.2",
|
||||
"description": "Monorepo for authentik.",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"compile": "NODE_OPTIONS=\"--max-old-space-size=3000\" tsc -b",
|
||||
"compile:clean": "node ./sdk/out/scripts/clean.js",
|
||||
"lint": "run-s lint:prettier:check lint:eslint:check",
|
||||
"lint:eslint:check": "eslint .",
|
||||
"lint:eslint:fix": "eslint --fix .",
|
||||
"lint:fix": "run-s lint:prettier:fix lint:eslint:fix",
|
||||
"lint:prettier": "eslint .",
|
||||
"lint:prettier:check": "prettier --cache --check -u .",
|
||||
"lint:prettier:fix": "prettier --cache --write -u ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-lit": "^2.0.0",
|
||||
"eslint-plugin-wc": "^3.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0",
|
||||
"zx": "^8.4.1"
|
||||
},
|
||||
"workspaces": [
|
||||
"gen-ts-api",
|
||||
"web",
|
||||
"website",
|
||||
"packages/*"
|
||||
],
|
||||
"prettier": "@goauthentik/prettier-config"
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.2.1",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @file TypeScript type definitions for eslint-plugin-react-hooks
|
||||
*/
|
||||
declare module "eslint-plugin-react-hooks" {
|
||||
import { ESLint } from "eslint";
|
||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
||||
type PluginInstance = ESLint.Plugin;
|
||||
const Plugin: PluginInstance;
|
||||
|
||||
export default Plugin;
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @file TypeScript type definitions for eslint-plugin-react
|
||||
*/
|
||||
declare module "eslint-plugin-react" {
|
||||
import { ESLint } from "eslint";
|
||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
||||
type PluginInstance = ESLint.Plugin;
|
||||
const Plugin: PluginInstance;
|
||||
|
||||
export default Plugin;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
# `@goauthentik/eslint-config`
|
||||
|
||||
This package contains the ESLint configuration used by authentik.
|
||||
While it is possible to use this configuration outside of our projects,
|
||||
you may find that it is not as useful as other popular configurations.
|
||||
@ -1,72 +0,0 @@
|
||||
import eslint from "@eslint/js";
|
||||
import { javaScriptConfig } from "@goauthentik/eslint-config/javascript-config";
|
||||
import { reactConfig } from "@goauthentik/eslint-config/react-config";
|
||||
import { typescriptConfig } from "@goauthentik/eslint-config/typescript-config";
|
||||
import * as litconf from "eslint-plugin-lit";
|
||||
import * as wcconf from "eslint-plugin-wc";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef ESLintPackageConfigOptions Options for creating package ESLint configuration.
|
||||
* @property {string[]} [ignorePatterns] Override ignore patterns for ESLint.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {string[]} Default Ignore patterns for ESLint.
|
||||
*/
|
||||
export const DefaultIgnorePatterns = [
|
||||
// ---
|
||||
"**/*.md",
|
||||
"**/out",
|
||||
"**/dist",
|
||||
"**/.wireit",
|
||||
"website/build/**",
|
||||
"website/.docusaurus/**",
|
||||
"**/node_modules",
|
||||
"**/coverage",
|
||||
"**/storybook-static",
|
||||
"**/locale-codes.ts",
|
||||
"**/src/locales",
|
||||
"**/gen-ts-api",
|
||||
];
|
||||
|
||||
/**
|
||||
* Given a preferred package name, creates a ESLint configuration object.
|
||||
*
|
||||
* @param {ESLintPackageConfigOptions} options The preferred package configuration options.
|
||||
*
|
||||
* @returns The ESLint configuration object.
|
||||
*/
|
||||
export function createESLintPackageConfig({ ignorePatterns = DefaultIgnorePatterns } = {}) {
|
||||
return tseslint.config(
|
||||
{
|
||||
ignores: ignorePatterns,
|
||||
},
|
||||
|
||||
eslint.configs.recommended,
|
||||
javaScriptConfig,
|
||||
|
||||
wcconf.configs["flat/recommended"],
|
||||
litconf.configs["flat/recommended"],
|
||||
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
...typescriptConfig,
|
||||
|
||||
...reactConfig,
|
||||
|
||||
{
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
},
|
||||
files: [
|
||||
// ---
|
||||
"**/scripts/**/*",
|
||||
"**/test/**/*",
|
||||
"**/tests/**/*",
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
// @ts-check
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
const MAX_DEPTH = 4;
|
||||
const MAX_NESTED_CALLBACKS = 4;
|
||||
const MAX_PARAMS = 5;
|
||||
|
||||
/**
|
||||
* ESLint configuration for JavaScript authentik projects.
|
||||
*/
|
||||
export const javaScriptConfig = tseslint.config({
|
||||
rules: {
|
||||
// TODO: Clean up before enabling.
|
||||
"accessor-pairs": "off",
|
||||
"array-callback-return": "error",
|
||||
"block-scoped-var": "error",
|
||||
"consistent-return": ["error", { treatUndefinedAsUnspecified: false }],
|
||||
"consistent-this": ["error", "that"],
|
||||
"curly": "off",
|
||||
"dot-notation": [
|
||||
"error",
|
||||
{
|
||||
allowKeywords: true,
|
||||
},
|
||||
],
|
||||
"eqeqeq": "error",
|
||||
"func-names": ["error", "as-needed"],
|
||||
"guard-for-in": "error",
|
||||
"max-depth": ["error", MAX_DEPTH],
|
||||
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
|
||||
"max-params": ["error", MAX_PARAMS],
|
||||
// TODO: Clean up before enabling.
|
||||
// "new-cap": "error",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-bitwise": [
|
||||
"error",
|
||||
{
|
||||
allow: ["~"],
|
||||
int32Hint: true,
|
||||
},
|
||||
],
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": "error",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty": "error",
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-function": ["error", { allow: ["constructors"] }],
|
||||
"no-labels": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-inner-declarations": ["error", "functions"],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-str": "error",
|
||||
// TODO: Clean up before enabling.
|
||||
"no-negated-condition": "off",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": ["error", { props: false }],
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-restricted-syntax": ["error", "WithStatement"],
|
||||
"no-script-url": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
// TODO: Clean up before enabling.
|
||||
// "no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "off", // Handled by Prettier.
|
||||
"no-undef": "off",
|
||||
"no-undef-init": "off",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"radix": "error",
|
||||
"require-yield": "error",
|
||||
"strict": ["error", "global"],
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error",
|
||||
"vars-on-top": "error",
|
||||
"yoda": ["error", "never"],
|
||||
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
// SonarJS is not yet compatible with ESLint 9. Commenting these out
|
||||
// until it is.
|
||||
// "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],
|
||||
// "sonarjs/no-duplicate-string": "off",
|
||||
// "sonarjs/no-nested-template-literals": "off",
|
||||
},
|
||||
});
|
||||
|
||||
export default javaScriptConfig;
|
||||
@ -1,53 +0,0 @@
|
||||
{
|
||||
"name": "@goauthentik/eslint-config",
|
||||
"version": "1.0.0",
|
||||
"description": "authentik's ESLint config",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
},
|
||||
"./react-config": {
|
||||
"import": "./react-config.js",
|
||||
"types": "./out/react-config.d.ts"
|
||||
},
|
||||
"./javascript-config": {
|
||||
"import": "./javascript-config.js",
|
||||
"types": "./out/javascript-config.d.ts"
|
||||
},
|
||||
"./typescript-config": {
|
||||
"import": "./typescript-config.js",
|
||||
"types": "./out/typescript-config.d.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-hooks": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/tsconfig": "1.0.0",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"react": "^18.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
34
packages/eslint-config/react-config.js
vendored
34
packages/eslint-config/react-config.js
vendored
@ -1,34 +0,0 @@
|
||||
import reactPlugin from "eslint-plugin-react";
|
||||
import hooksPlugin from "eslint-plugin-react-hooks";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
/**
|
||||
* ESLint configuration for React authentik projects.
|
||||
*/
|
||||
export const reactConfig = tseslint.config({
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
|
||||
plugins: {
|
||||
"react": reactPlugin,
|
||||
"react-hooks": hooksPlugin,
|
||||
},
|
||||
|
||||
rules: {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
|
||||
"react/jsx-uses-react": 0,
|
||||
|
||||
"react/display-name": "off",
|
||||
"react/jsx-curly-brace-presence": "error",
|
||||
"react/jsx-no-leaked-render": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
});
|
||||
|
||||
export default reactConfig;
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
// @ts-check
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
/**
|
||||
* ESLint configuration for TypeScript authentik projects.
|
||||
*/
|
||||
export const typescriptConfig = tseslint.config({
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-comment": [
|
||||
"error",
|
||||
{
|
||||
"ts-expect-error": "allow-with-description",
|
||||
"ts-ignore": true,
|
||||
"ts-nocheck": "allow-with-description",
|
||||
"ts-check": false,
|
||||
"minimumDescriptionLength": 5,
|
||||
},
|
||||
],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export default typescriptConfig;
|
||||
@ -1,18 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@ -1,5 +0,0 @@
|
||||
# `@goauthentik/monorepo`
|
||||
|
||||
This package contains utility scripts common to all TypeScript and JavaScript packages in the
|
||||
`@goauthentik` monorepo.
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @file Constants for JavaScript and TypeScript files.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current Node.js environment, defaulting to "development" when not set.
|
||||
*
|
||||
* Note, this should only be used during the build process.
|
||||
*
|
||||
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
|
||||
* ensure that module tree-shaking works correctly.
|
||||
*
|
||||
*/
|
||||
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
|
||||
process.env.NODE_ENV || "development"
|
||||
);
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./paths.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./version.js";
|
||||
export * from "./scripting.js";
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@goauthentik/monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Utilities for the authentik monorepo.",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @typedef {'~authentik'} MonoRepoRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* The root of the authentik monorepo.
|
||||
*/
|
||||
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Resolve a package name to its location in the monorepo to the single node_modules directory.
|
||||
* @param {string} packageName
|
||||
* @returns {string} The resolved path to the package.
|
||||
* @throws {Error} If the package cannot be resolved.
|
||||
*/
|
||||
export function resolvePackage(packageName) {
|
||||
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
|
||||
paths: [MonoRepoRoot],
|
||||
});
|
||||
|
||||
return dirname(packageJSONPath);
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { createRequire } from "module";
|
||||
import * as path from "path";
|
||||
import * as process from "process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
/**
|
||||
* Predicate to determine if a module was run directly, i.e. not imported.
|
||||
*
|
||||
* @param {ImportMeta} meta The `import.meta` object of the module.
|
||||
*
|
||||
* @return {boolean} Whether the module was run directly.
|
||||
*/
|
||||
export function isMain(meta) {
|
||||
// Are we not in a module context?
|
||||
if (!meta) return false;
|
||||
|
||||
const relativeScriptPath = process.argv[1];
|
||||
|
||||
if (!relativeScriptPath) return false;
|
||||
|
||||
const require = createRequire(meta.url);
|
||||
const absoluteScriptPath = require.resolve(relativeScriptPath);
|
||||
|
||||
const modulePath = fileURLToPath(meta.url);
|
||||
|
||||
const scriptExtension = path.extname(absoluteScriptPath);
|
||||
|
||||
if (scriptExtension) {
|
||||
return modulePath === absoluteScriptPath;
|
||||
}
|
||||
|
||||
const moduleExtension = path.extname(modulePath);
|
||||
|
||||
if (moduleExtension) {
|
||||
return absoluteScriptPath === modulePath.slice(0, -moduleExtension.length);
|
||||
}
|
||||
|
||||
// If both are without extension, compare them directly.
|
||||
return modulePath === absoluteScriptPath;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
import PackageJSON from "../../package.json" with { type: "json" };
|
||||
import { MonoRepoRoot } from "./paths.js";
|
||||
|
||||
/**
|
||||
* The current version of authentik in SemVer format.
|
||||
*
|
||||
*/
|
||||
export const AuthentikVersion = /**@type {`${number}.${number}.${number}`} */ (PackageJSON.version);
|
||||
|
||||
/**
|
||||
* Reads the last commit hash from the current git repository.
|
||||
*/
|
||||
export function readGitBuildHash() {
|
||||
try {
|
||||
const commit = execSync("git rev-parse HEAD", {
|
||||
encoding: "utf8",
|
||||
cwd: MonoRepoRoot,
|
||||
})
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
return commit;
|
||||
} catch (_error) {
|
||||
console.debug("Git commit could not be read.");
|
||||
}
|
||||
|
||||
return process.env.GIT_BUILD_HASH || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the build identifier for the current environment.
|
||||
*
|
||||
* This must match the behavior defined in authentik's server-side `get_full_version` function.
|
||||
*
|
||||
* @see {@link "authentik\_\_init\_\_.py"}
|
||||
*/
|
||||
export function readBuildIdentifier() {
|
||||
const { GIT_BUILD_HASH = "d72def036820985a909266e8167ccb8087c7ce32" } = process.env;
|
||||
|
||||
if (!GIT_BUILD_HASH) return AuthentikVersion;
|
||||
|
||||
return [AuthentikVersion, GIT_BUILD_HASH].join("+");
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@ -1,5 +0,0 @@
|
||||
# `@goauthentik/prettier-config`
|
||||
|
||||
This package contains the Prettier configuration used by authentik.
|
||||
While it is possible to use this configuration outside of our projects,
|
||||
you may find that it is not as useful as other popular configurations.
|
||||
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* @file Prettier configuration for authentik.
|
||||
*
|
||||
* @import { Config as PrettierConfig } from "prettier";
|
||||
* @import { PluginConfig as SortPluginConfig } from "@trivago/prettier-plugin-sort-imports";
|
||||
*
|
||||
* @typedef {object} PackageJSONPluginConfig
|
||||
* @property {string[]} [packageSortOrder] Custom ordering array.
|
||||
*/
|
||||
|
||||
/**
|
||||
* authentik Prettier configuration.
|
||||
*
|
||||
* @type {PrettierConfig & SortPluginConfig & PackageJSONPluginConfig}
|
||||
* @internal
|
||||
*/
|
||||
export const AuthentikPrettierConfig = {
|
||||
arrowParens: "always",
|
||||
bracketSpacing: true,
|
||||
embeddedLanguageFormatting: "auto",
|
||||
htmlWhitespaceSensitivity: "css",
|
||||
insertPragma: false,
|
||||
jsxSingleQuote: false,
|
||||
printWidth: 100,
|
||||
proseWrap: "preserve",
|
||||
quoteProps: "consistent",
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
tabWidth: 4,
|
||||
trailingComma: "all",
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: false,
|
||||
plugins: ["prettier-plugin-packagejson", "@trivago/prettier-plugin-sort-imports"],
|
||||
importOrder: ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
|
||||
overrides: [
|
||||
{
|
||||
files: "schemas/**/*.json",
|
||||
options: {
|
||||
tabWidth: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "tsconfig.json",
|
||||
options: {
|
||||
trailingComma: "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "package.json",
|
||||
options: {
|
||||
packageSortOrder: [
|
||||
// ---
|
||||
"name",
|
||||
"version",
|
||||
"description",
|
||||
"license",
|
||||
"private",
|
||||
"author",
|
||||
"authors",
|
||||
"scripts",
|
||||
"main",
|
||||
"type",
|
||||
"exports",
|
||||
"imports",
|
||||
"dependencies",
|
||||
"devDependencies",
|
||||
"peerDependencies",
|
||||
"optionalDependencies",
|
||||
"wireit",
|
||||
"resolutions",
|
||||
"engines",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { format } from "prettier";
|
||||
|
||||
import { AuthentikPrettierConfig } from "./config.js";
|
||||
|
||||
/**
|
||||
* Format using Prettier.
|
||||
*
|
||||
* Defaults to using the TypeScript parser.
|
||||
*
|
||||
* @category Formatting
|
||||
* @param {string} fileContents The contents of the file to format.
|
||||
*
|
||||
* @returns {Promise<string>} The formatted file contents.
|
||||
*/
|
||||
export function formatWithPrettier(fileContents) {
|
||||
return format(fileContents, {
|
||||
...AuthentikPrettierConfig,
|
||||
parser: "typescript",
|
||||
});
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { AuthentikPrettierConfig } from "./config.js";
|
||||
|
||||
export * from "./config.js";
|
||||
export * from "./format.js";
|
||||
|
||||
export default AuthentikPrettierConfig;
|
||||
@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "@goauthentik/prettier-config",
|
||||
"version": "1.0.0",
|
||||
"description": "authentik's Prettier config",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"peerDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user