Compare commits
	
		
			133 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4b33971155 | |||
| 9e71287c25 | |||
| 9784c6c828 | |||
| 732b6a3556 | |||
| dc1e17ba0c | |||
| f05d5973af | |||
| deb48487f3 | |||
| 78f3abc64f | |||
| e45bc3834a | |||
| 0d9db1b6f2 | |||
| ce555aa5e9 | |||
| 07ca82e599 | |||
| a9339589bb | |||
| c8ed650f1c | |||
| cd78d8d3fa | |||
| 7fdc935fb9 | |||
| c8069325b3 | |||
| 9d08e02fe1 | |||
| a11ea598a2 | |||
| 2713b05e8c | |||
| fef5a5ca52 | |||
| 9d339d8b11 | |||
| 4e86aa3f59 | |||
| 221e4b665c | |||
| e67f235a9f | |||
| 741ebbacca | |||
| b63b789f77 | |||
| a63702ef90 | |||
| a4a4550753 | |||
| fd864655f6 | |||
| c1da09507a | |||
| ed2ea220bf | |||
| 7738cbe751 | |||
| bf16ea3607 | |||
| d6f44e069c | |||
| 899cf392f4 | |||
| d99451b45c | |||
| 5b31f8edf6 | |||
| 00235e039b | |||
| 2dfaef4220 | |||
| 13fceacfe4 | |||
| f8dc32b387 | |||
| 828f2f8b92 | |||
| 734399755d | |||
| d8f106b976 | |||
| 9a524dd671 | |||
| 0775296003 | |||
| 390534c14e | |||
| 2a644f64ad | |||
| e0298141cf | |||
| df7119bb22 | |||
| 1d5bba831e | |||
| 0b4be70c00 | |||
| 786737650b | |||
| 54c80a2e1f | |||
| b376211a0e | |||
| 1990a3063e | |||
| 5abf22ad8a | |||
| b7b87d87fc | |||
| 20184424ab | |||
| d5de12b69e | |||
| d1a3350085 | |||
| e0b84c71a7 | |||
| 3bc1d6a690 | |||
| 786c74ef2c | |||
| 3e9b5f5449 | |||
| 5d071488d3 | |||
| 90d234a458 | |||
| 0032bb6aee | |||
| 6e6755d805 | |||
| 132b990f10 | |||
| 34a3d81eff | |||
| 43a4217497 | |||
| e0ec5826ca | |||
| 5413a01360 | |||
| d9c3a29404 | |||
| bcce91476c | |||
| 56f0f454d0 | |||
| 25e63edf77 | |||
| d150851ff5 | |||
| 2e2840c71e | |||
| ff276fcc58 | |||
| 2852fa3c5e | |||
| 1c6d498621 | |||
| 3f0e4bb654 | |||
| a59d78a7c7 | |||
| 0a24202f1e | |||
| cbc86d674d | |||
| 082628771b | |||
| 93b50e7d6e | |||
| c6de4e47d7 | |||
| 0e9e378bdf | |||
| de4b3d6290 | |||
| 56f75aecc7 | |||
| 0fe009d37c | |||
| 49db283e71 | |||
| 7058366623 | |||
| ced45513b8 | |||
| 15e15c9635 | |||
| d53c82eee2 | |||
| e1e0b0cf7d | |||
| 33e013a59f | |||
| 96a74776f8 | |||
| bb63d08682 | |||
| 32655567da | |||
| ff5f5f65e8 | |||
| 1f97aa09fa | |||
| 32e5ebb8a3 | |||
| 597e00dd86 | |||
| dd31191845 | |||
| e9d95b1311 | |||
| 3319547a0e | |||
| 1a00730cdd | |||
| 466723573c | |||
| ea784d47f4 | |||
| 77d5ba2862 | |||
| f4580a1097 | |||
| 9e3d1f0baa | |||
| c002c4b610 | |||
| dde5e910cf | |||
| 5218332bce | |||
| 28cd08bbba | |||
| 3cb0575a1e | |||
| dc1c1b9569 | |||
| 662d117b66 | |||
| b2449757f9 | |||
| a0753bfc88 | |||
| e2a771bdaa | |||
| 23de9df2a5 | |||
| 5c739ebed2 | |||
| d3f8d7120f | |||
| 21fd251edf | |||
| 28cededb90 | 
@ -1,9 +1,11 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 2021.3.1-rc2
 | 
			
		||||
current_version = 2021.3.4
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
 | 
			
		||||
serialize = {major}.{minor}.{patch}-{release}
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
 | 
			
		||||
serialize = 
 | 
			
		||||
	{major}.{minor}.{patch}-{release}
 | 
			
		||||
	{major}.{minor}.{patch}
 | 
			
		||||
message = release: {new_version}
 | 
			
		||||
tag_name = version/{new_version}
 | 
			
		||||
 | 
			
		||||
@ -34,3 +36,7 @@ values =
 | 
			
		||||
[bumpversion:file:outpost/pkg/version.go]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:web/src/constants.ts]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@ -18,11 +18,11 @@ jobs:
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        run: docker build
 | 
			
		||||
          --no-cache
 | 
			
		||||
          -t beryju/authentik:2021.3.1-rc2
 | 
			
		||||
          -t beryju/authentik:2021.3.4
 | 
			
		||||
          -t beryju/authentik:latest
 | 
			
		||||
          -f Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/authentik:2021.3.1-rc2
 | 
			
		||||
        run: docker push beryju/authentik:2021.3.4
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/authentik:latest
 | 
			
		||||
  build-proxy:
 | 
			
		||||
@ -48,17 +48,20 @@ jobs:
 | 
			
		||||
          cd outpost/
 | 
			
		||||
          docker build \
 | 
			
		||||
          --no-cache \
 | 
			
		||||
          -t beryju/authentik-proxy:2021.3.1-rc2 \
 | 
			
		||||
          -t beryju/authentik-proxy:2021.3.4 \
 | 
			
		||||
          -t beryju/authentik-proxy:latest \
 | 
			
		||||
          -f proxy.Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/authentik-proxy:2021.3.1-rc2
 | 
			
		||||
        run: docker push beryju/authentik-proxy:2021.3.4
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/authentik-proxy:latest
 | 
			
		||||
  build-static:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - name: prepare ts api client
 | 
			
		||||
        run: |
 | 
			
		||||
          docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
 | 
			
		||||
      - name: Docker Login Registry
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
@ -69,11 +72,11 @@ jobs:
 | 
			
		||||
          cd web/
 | 
			
		||||
          docker build \
 | 
			
		||||
          --no-cache \
 | 
			
		||||
          -t beryju/authentik-static:2021.3.1-rc2 \
 | 
			
		||||
          -t beryju/authentik-static:2021.3.4 \
 | 
			
		||||
          -t beryju/authentik-static:latest \
 | 
			
		||||
          -f Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/authentik-static:2021.3.1-rc2
 | 
			
		||||
        run: docker push beryju/authentik-static:2021.3.4
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/authentik-static:latest
 | 
			
		||||
  test-release:
 | 
			
		||||
@ -107,5 +110,5 @@ jobs:
 | 
			
		||||
          SENTRY_PROJECT: authentik
 | 
			
		||||
          SENTRY_URL: https://sentry.beryju.org
 | 
			
		||||
        with:
 | 
			
		||||
          tagName: 2021.3.1-rc2
 | 
			
		||||
          tagName: 2021.3.4
 | 
			
		||||
          environment: beryjuorg-prod
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,9 @@ WORKDIR /
 | 
			
		||||
COPY --from=locker /app/requirements.txt /
 | 
			
		||||
COPY --from=locker /app/requirements-dev.txt /
 | 
			
		||||
 | 
			
		||||
ARG GIT_BUILD_HASH
 | 
			
		||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
 | 
			
		||||
 | 
			
		||||
RUN apt-get update && \
 | 
			
		||||
    apt-get install -y --no-install-recommends curl ca-certificates gnupg && \
 | 
			
		||||
    curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
 | 
			
		||||
@ -45,4 +48,5 @@ COPY ./lifecycle/ /lifecycle
 | 
			
		||||
USER authentik
 | 
			
		||||
STOPSIGNAL SIGINT
 | 
			
		||||
ENV TMPDIR /dev/shm/
 | 
			
		||||
ENV PYTHONUBUFFERED 1
 | 
			
		||||
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										192
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										192
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -18,45 +18,45 @@
 | 
			
		||||
    "default": {
 | 
			
		||||
        "aiohttp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0",
 | 
			
		||||
                "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6",
 | 
			
		||||
                "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf",
 | 
			
		||||
                "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9",
 | 
			
		||||
                "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e",
 | 
			
		||||
                "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0",
 | 
			
		||||
                "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329",
 | 
			
		||||
                "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2",
 | 
			
		||||
                "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40",
 | 
			
		||||
                "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a",
 | 
			
		||||
                "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4",
 | 
			
		||||
                "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de",
 | 
			
		||||
                "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9",
 | 
			
		||||
                "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9",
 | 
			
		||||
                "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb",
 | 
			
		||||
                "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076",
 | 
			
		||||
                "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de",
 | 
			
		||||
                "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907",
 | 
			
		||||
                "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d",
 | 
			
		||||
                "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536",
 | 
			
		||||
                "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d",
 | 
			
		||||
                "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54",
 | 
			
		||||
                "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc",
 | 
			
		||||
                "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212",
 | 
			
		||||
                "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9",
 | 
			
		||||
                "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d",
 | 
			
		||||
                "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b",
 | 
			
		||||
                "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7",
 | 
			
		||||
                "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81",
 | 
			
		||||
                "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c",
 | 
			
		||||
                "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895",
 | 
			
		||||
                "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297",
 | 
			
		||||
                "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb",
 | 
			
		||||
                "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe",
 | 
			
		||||
                "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242",
 | 
			
		||||
                "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0",
 | 
			
		||||
                "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"
 | 
			
		||||
                "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe",
 | 
			
		||||
                "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe",
 | 
			
		||||
                "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5",
 | 
			
		||||
                "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8",
 | 
			
		||||
                "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd",
 | 
			
		||||
                "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb",
 | 
			
		||||
                "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c",
 | 
			
		||||
                "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87",
 | 
			
		||||
                "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0",
 | 
			
		||||
                "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290",
 | 
			
		||||
                "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5",
 | 
			
		||||
                "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287",
 | 
			
		||||
                "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde",
 | 
			
		||||
                "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf",
 | 
			
		||||
                "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8",
 | 
			
		||||
                "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16",
 | 
			
		||||
                "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf",
 | 
			
		||||
                "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809",
 | 
			
		||||
                "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213",
 | 
			
		||||
                "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f",
 | 
			
		||||
                "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013",
 | 
			
		||||
                "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b",
 | 
			
		||||
                "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9",
 | 
			
		||||
                "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5",
 | 
			
		||||
                "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb",
 | 
			
		||||
                "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df",
 | 
			
		||||
                "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4",
 | 
			
		||||
                "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439",
 | 
			
		||||
                "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f",
 | 
			
		||||
                "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22",
 | 
			
		||||
                "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f",
 | 
			
		||||
                "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5",
 | 
			
		||||
                "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970",
 | 
			
		||||
                "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009",
 | 
			
		||||
                "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc",
 | 
			
		||||
                "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
 | 
			
		||||
                "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.7.4"
 | 
			
		||||
            "version": "==3.7.4.post0"
 | 
			
		||||
        },
 | 
			
		||||
        "aioredis": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -95,10 +95,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "autobahn": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:884f79c50fdc55ade2c315946a9caa145e8b10075eee9d2c2594ea5e8f5226aa",
 | 
			
		||||
                "sha256:bf7a9d302a34d0f719d43c57f65ca1f2f5c982dd6ea0c11e1e190ef6f43710fe"
 | 
			
		||||
                "sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
 | 
			
		||||
                "sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==21.2.2"
 | 
			
		||||
            "version": "==21.3.1"
 | 
			
		||||
        },
 | 
			
		||||
        "automat": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -116,18 +116,17 @@
 | 
			
		||||
        },
 | 
			
		||||
        "boto3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3570a3c0fbd80bcb30449f87cf9d2f7abb67fac2a5e317d002f9921c59be9b17",
 | 
			
		||||
                "sha256:ceff2f32ba05acc9ee35a6dd82e29ea285d63e889bed39a6ba7a700146f43749"
 | 
			
		||||
                "sha256:64a8900b3a110e2d6ff4d87f4d8cd56f0c8527361d9fc9385fcb50efe7a4975a",
 | 
			
		||||
                "sha256:8e9ff8006c41889ed8a11831dee62adf922e071f14d54c52946d1f7855ae7a8e"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.17.18"
 | 
			
		||||
            "version": "==1.17.26"
 | 
			
		||||
        },
 | 
			
		||||
        "botocore": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:51900b10da4ae45be4b16045e5b2ff7d1158a7955d9d7cc5e5a9ba3170f10586",
 | 
			
		||||
                "sha256:b181f32d9075e5419a89fa9636ce95946c15459c9bfadfabb53ca902fc8072b8"
 | 
			
		||||
                "sha256:4a785847a351e59f2329627fc9a19cf50f07644ea68996a1595d5a20487a423f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.20.18"
 | 
			
		||||
            "version": "==1.20.26"
 | 
			
		||||
        },
 | 
			
		||||
        "cachetools": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -217,10 +216,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "chardet": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
 | 
			
		||||
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
 | 
			
		||||
                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
 | 
			
		||||
                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.4"
 | 
			
		||||
            "version": "==4.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "click": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -304,11 +303,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "defusedxml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
 | 
			
		||||
                "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
 | 
			
		||||
                "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
 | 
			
		||||
                "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.6.0"
 | 
			
		||||
            "version": "==0.7.1"
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -445,10 +444,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "google-auth": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65",
 | 
			
		||||
                "sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e"
 | 
			
		||||
                "sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f",
 | 
			
		||||
                "sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.27.0"
 | 
			
		||||
            "version": "==1.27.1"
 | 
			
		||||
        },
 | 
			
		||||
        "gunicorn": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -817,10 +816,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "prompt-toolkit": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
 | 
			
		||||
                "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
 | 
			
		||||
                "sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a",
 | 
			
		||||
                "sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.16"
 | 
			
		||||
            "version": "==3.0.17"
 | 
			
		||||
        },
 | 
			
		||||
        "psycopg2-binary": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1024,15 +1023,23 @@
 | 
			
		||||
                "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
 | 
			
		||||
                "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
 | 
			
		||||
                "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
 | 
			
		||||
                "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
 | 
			
		||||
                "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
 | 
			
		||||
                "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
 | 
			
		||||
                "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
 | 
			
		||||
                "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
 | 
			
		||||
                "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
 | 
			
		||||
                "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
 | 
			
		||||
                "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
 | 
			
		||||
                "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
 | 
			
		||||
                "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
 | 
			
		||||
                "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
 | 
			
		||||
                "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
 | 
			
		||||
                "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
 | 
			
		||||
                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
 | 
			
		||||
                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
 | 
			
		||||
                "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
 | 
			
		||||
                "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
 | 
			
		||||
                "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==5.4.1"
 | 
			
		||||
@ -1069,10 +1076,47 @@
 | 
			
		||||
        },
 | 
			
		||||
        "ruamel.yaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5",
 | 
			
		||||
                "sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"
 | 
			
		||||
                "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3",
 | 
			
		||||
                "sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.16.12"
 | 
			
		||||
            "version": "==0.16.13"
 | 
			
		||||
        },
 | 
			
		||||
        "ruamel.yaml.clib": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b",
 | 
			
		||||
                "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f",
 | 
			
		||||
                "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c",
 | 
			
		||||
                "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91",
 | 
			
		||||
                "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc",
 | 
			
		||||
                "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7",
 | 
			
		||||
                "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3",
 | 
			
		||||
                "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7",
 | 
			
		||||
                "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6",
 | 
			
		||||
                "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6",
 | 
			
		||||
                "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd",
 | 
			
		||||
                "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0",
 | 
			
		||||
                "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62",
 | 
			
		||||
                "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99",
 | 
			
		||||
                "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5",
 | 
			
		||||
                "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026",
 | 
			
		||||
                "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb",
 | 
			
		||||
                "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2",
 | 
			
		||||
                "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1",
 | 
			
		||||
                "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4",
 | 
			
		||||
                "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b",
 | 
			
		||||
                "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923",
 | 
			
		||||
                "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e",
 | 
			
		||||
                "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
 | 
			
		||||
                "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
 | 
			
		||||
                "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
 | 
			
		||||
                "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
 | 
			
		||||
                "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
 | 
			
		||||
                "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
 | 
			
		||||
                "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
 | 
			
		||||
                "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "platform_python_implementation == 'CPython' and python_version < '3.10'",
 | 
			
		||||
            "version": "==0.2.2"
 | 
			
		||||
        },
 | 
			
		||||
        "s3transfer": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1083,11 +1127,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "sentry-sdk": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237",
 | 
			
		||||
                "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b"
 | 
			
		||||
                "sha256:71de00c9711926816f750bc0f57ef2abbcb1bfbdf5378c601df7ec978f44857a",
 | 
			
		||||
                "sha256:9221e985f425913204989d0e0e1cbb719e8b7fa10540f1bc509f660c06a34e66"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.20.3"
 | 
			
		||||
            "version": "==1.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "service-identity": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1249,10 +1293,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "websocket-client": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
 | 
			
		||||
                "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
 | 
			
		||||
                "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663",
 | 
			
		||||
                "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.57.0"
 | 
			
		||||
            "version": "==0.58.0"
 | 
			
		||||
        },
 | 
			
		||||
        "websockets": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1765,15 +1809,23 @@
 | 
			
		||||
                "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
 | 
			
		||||
                "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
 | 
			
		||||
                "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
 | 
			
		||||
                "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
 | 
			
		||||
                "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
 | 
			
		||||
                "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
 | 
			
		||||
                "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
 | 
			
		||||
                "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
 | 
			
		||||
                "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
 | 
			
		||||
                "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
 | 
			
		||||
                "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
 | 
			
		||||
                "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
 | 
			
		||||
                "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
 | 
			
		||||
                "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
 | 
			
		||||
                "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
 | 
			
		||||
                "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
 | 
			
		||||
                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
 | 
			
		||||
                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
 | 
			
		||||
                "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
 | 
			
		||||
                "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
 | 
			
		||||
                "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==5.4.1"
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,3 @@
 | 
			
		||||
"""authentik"""
 | 
			
		||||
__version__ = "2021.3.1-rc2"
 | 
			
		||||
__version__ = "2021.3.4"
 | 
			
		||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,8 @@ from django.db.models import Count, ExpressionWrapper, F, Model
 | 
			
		||||
from django.db.models.fields import DurationField
 | 
			
		||||
from django.db.models.functions import ExtractHour
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
from rest_framework.fields import SerializerMethodField
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
 | 
			
		||||
from rest_framework.fields import IntegerField, SerializerMethodField
 | 
			
		||||
from rest_framework.permissions import IsAdminUser
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
@ -37,23 +37,39 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
 | 
			
		||||
    for hour in range(0, -24, -1):
 | 
			
		||||
        results.append(
 | 
			
		||||
            {
 | 
			
		||||
                "x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
 | 
			
		||||
                "y": data[hour * -1],
 | 
			
		||||
                "x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
 | 
			
		||||
                * 1000,
 | 
			
		||||
                "y_cord": data[hour * -1],
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    return results
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdministrationMetricsSerializer(Serializer):
 | 
			
		||||
class CoordinateSerializer(Serializer):
 | 
			
		||||
    """Coordinates for diagrams"""
 | 
			
		||||
 | 
			
		||||
    x_cord = IntegerField(read_only=True)
 | 
			
		||||
    y_cord = IntegerField(read_only=True)
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def update(self, instance: Model, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoginMetricsSerializer(Serializer):
 | 
			
		||||
    """Login Metrics per 1h"""
 | 
			
		||||
 | 
			
		||||
    logins_per_1h = SerializerMethodField()
 | 
			
		||||
    logins_failed_per_1h = SerializerMethodField()
 | 
			
		||||
 | 
			
		||||
    @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
 | 
			
		||||
    def get_logins_per_1h(self, _):
 | 
			
		||||
        """Get successful logins per hour for the last 24 hours"""
 | 
			
		||||
        return get_events_per_1h(action=EventAction.LOGIN)
 | 
			
		||||
 | 
			
		||||
    @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
 | 
			
		||||
    def get_logins_failed_per_1h(self, _):
 | 
			
		||||
        """Get failed logins per hour for the last 24 hours"""
 | 
			
		||||
        return get_events_per_1h(action=EventAction.LOGIN_FAILED)
 | 
			
		||||
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
 | 
			
		||||
 | 
			
		||||
    permission_classes = [IsAdminUser]
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
 | 
			
		||||
    @swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
 | 
			
		||||
    def list(self, request: Request) -> Response:
 | 
			
		||||
        """Login Metrics per 1h"""
 | 
			
		||||
        serializer = AdministrationMetricsSerializer(True)
 | 
			
		||||
        serializer = LoginMetricsSerializer(True)
 | 
			
		||||
        return Response(serializer.data)
 | 
			
		||||
 | 
			
		||||
@ -7,14 +7,14 @@ from django.http.response import Http404
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
from rest_framework.decorators import action
 | 
			
		||||
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
 | 
			
		||||
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
 | 
			
		||||
from rest_framework.permissions import IsAdminUser
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import Serializer
 | 
			
		||||
from rest_framework.viewsets import ViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.events.monitored_tasks import TaskInfo
 | 
			
		||||
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaskSerializer(Serializer):
 | 
			
		||||
@ -24,7 +24,10 @@ class TaskSerializer(Serializer):
 | 
			
		||||
    task_description = CharField()
 | 
			
		||||
    task_finish_timestamp = DateTimeField(source="finish_timestamp")
 | 
			
		||||
 | 
			
		||||
    status = IntegerField(source="result.status.value")
 | 
			
		||||
    status = ChoiceField(
 | 
			
		||||
        source="result.status.name",
 | 
			
		||||
        choices=[(x.name, x.name) for x in TaskResultStatus],
 | 
			
		||||
    )
 | 
			
		||||
    messages = ListField(source="result.messages")
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data: dict) -> Model:
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
"""authentik administration overview"""
 | 
			
		||||
from os import environ
 | 
			
		||||
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.db.models import Model
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
@ -11,7 +13,7 @@ from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import Serializer
 | 
			
		||||
from rest_framework.viewsets import GenericViewSet
 | 
			
		||||
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik import ENV_GIT_HASH_KEY, __version__
 | 
			
		||||
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,8 +22,13 @@ class VersionSerializer(Serializer):
 | 
			
		||||
 | 
			
		||||
    version_current = SerializerMethodField()
 | 
			
		||||
    version_latest = SerializerMethodField()
 | 
			
		||||
    build_hash = SerializerMethodField()
 | 
			
		||||
    outdated = SerializerMethodField()
 | 
			
		||||
 | 
			
		||||
    def get_build_hash(self, _) -> str:
 | 
			
		||||
        """Get build hash, if version is not latest or released"""
 | 
			
		||||
        return environ.get(ENV_GIT_HASH_KEY, "")
 | 
			
		||||
 | 
			
		||||
    def get_version_current(self, _) -> str:
 | 
			
		||||
        """Get current version"""
 | 
			
		||||
        return __version__
 | 
			
		||||
@ -55,7 +62,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet):
 | 
			
		||||
    def get_queryset(self):  # pragma: no cover
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(responses={200: VersionSerializer(many=True)})
 | 
			
		||||
    @swagger_auto_schema(responses={200: VersionSerializer(many=False)})
 | 
			
		||||
    def list(self, request: Request) -> Response:
 | 
			
		||||
        """Get running and latest version."""
 | 
			
		||||
        return Response(VersionSerializer(True).data)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
			
		||||
"""Swagger Pagination Schema class"""
 | 
			
		||||
from typing import OrderedDict
 | 
			
		||||
 | 
			
		||||
from drf_yasg2 import openapi
 | 
			
		||||
from drf_yasg2.inspectors import PaginatorInspector
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PaginationInspector(PaginatorInspector):
 | 
			
		||||
    """Swagger Pagination Schema class"""
 | 
			
		||||
 | 
			
		||||
    def get_paginated_response(self, paginator, response_schema):
 | 
			
		||||
        """
 | 
			
		||||
        :param BasePagination paginator: the paginator
 | 
			
		||||
        :param openapi.Schema response_schema: the response schema that must be paged.
 | 
			
		||||
        :rtype: openapi.Schema
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        return openapi.Schema(
 | 
			
		||||
            type=openapi.TYPE_OBJECT,
 | 
			
		||||
            properties=OrderedDict(
 | 
			
		||||
                (
 | 
			
		||||
                    (
 | 
			
		||||
                        "pagination",
 | 
			
		||||
                        openapi.Schema(
 | 
			
		||||
                            type=openapi.TYPE_OBJECT,
 | 
			
		||||
                            properties=OrderedDict(
 | 
			
		||||
                                (
 | 
			
		||||
                                    ("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
 | 
			
		||||
                                    (
 | 
			
		||||
                                        "previous",
 | 
			
		||||
                                        openapi.Schema(type=openapi.TYPE_NUMBER),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    ("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
 | 
			
		||||
                                    (
 | 
			
		||||
                                        "current",
 | 
			
		||||
                                        openapi.Schema(type=openapi.TYPE_NUMBER),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    (
 | 
			
		||||
                                        "total_pages",
 | 
			
		||||
                                        openapi.Schema(type=openapi.TYPE_NUMBER),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    (
 | 
			
		||||
                                        "start_index",
 | 
			
		||||
                                        openapi.Schema(type=openapi.TYPE_NUMBER),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    (
 | 
			
		||||
                                        "end_index",
 | 
			
		||||
                                        openapi.Schema(type=openapi.TYPE_NUMBER),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )
 | 
			
		||||
                            ),
 | 
			
		||||
                            required=[
 | 
			
		||||
                                "next",
 | 
			
		||||
                                "previous",
 | 
			
		||||
                                "count",
 | 
			
		||||
                                "current",
 | 
			
		||||
                                "total_pages",
 | 
			
		||||
                                "start_index",
 | 
			
		||||
                                "end_index",
 | 
			
		||||
                            ],
 | 
			
		||||
                        ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    ("results", response_schema),
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
            required=["results", "pagination"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_paginator_parameters(self, paginator):
 | 
			
		||||
        """
 | 
			
		||||
        Get the pagination parameters for a single paginator **instance**.
 | 
			
		||||
 | 
			
		||||
        Should return :data:`.NotHandled` if this inspector
 | 
			
		||||
        does not know how to handle the given `paginator`.
 | 
			
		||||
 | 
			
		||||
        :param BasePagination paginator: the paginator
 | 
			
		||||
        :rtype: list[openapi.Parameter]
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            openapi.Parameter(
 | 
			
		||||
                "page",
 | 
			
		||||
                openapi.IN_QUERY,
 | 
			
		||||
                "Page Index",
 | 
			
		||||
                False,
 | 
			
		||||
                None,
 | 
			
		||||
                openapi.TYPE_INTEGER,
 | 
			
		||||
            ),
 | 
			
		||||
            openapi.Parameter(
 | 
			
		||||
                "page_size",
 | 
			
		||||
                openapi.IN_QUERY,
 | 
			
		||||
                "Page Size",
 | 
			
		||||
                False,
 | 
			
		||||
                None,
 | 
			
		||||
                openapi.TYPE_INTEGER,
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
@ -1,10 +1,11 @@
 | 
			
		||||
"""core Configs API"""
 | 
			
		||||
from django.db.models import Model
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
from rest_framework.fields import BooleanField, CharField
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import ReadOnlyField, Serializer
 | 
			
		||||
from rest_framework.serializers import Serializer
 | 
			
		||||
from rest_framework.viewsets import ViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
@ -13,12 +14,12 @@ from authentik.lib.config import CONFIG
 | 
			
		||||
class ConfigSerializer(Serializer):
 | 
			
		||||
    """Serialize authentik Config into DRF Object"""
 | 
			
		||||
 | 
			
		||||
    branding_logo = ReadOnlyField()
 | 
			
		||||
    branding_title = ReadOnlyField()
 | 
			
		||||
    branding_logo = CharField(read_only=True)
 | 
			
		||||
    branding_title = CharField(read_only=True)
 | 
			
		||||
 | 
			
		||||
    error_reporting_enabled = ReadOnlyField()
 | 
			
		||||
    error_reporting_environment = ReadOnlyField()
 | 
			
		||||
    error_reporting_send_pii = ReadOnlyField()
 | 
			
		||||
    error_reporting_enabled = BooleanField(read_only=True)
 | 
			
		||||
    error_reporting_environment = CharField(read_only=True)
 | 
			
		||||
    error_reporting_send_pii = BooleanField(read_only=True)
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
@ -32,7 +33,7 @@ class ConfigsViewSet(ViewSet):
 | 
			
		||||
 | 
			
		||||
    permission_classes = [AllowAny]
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(responses={200: ConfigSerializer(many=True)})
 | 
			
		||||
    @swagger_auto_schema(responses={200: ConfigSerializer(many=False)})
 | 
			
		||||
    def list(self, request: Request) -> Response:
 | 
			
		||||
        """Retrive public configuration options"""
 | 
			
		||||
        config = ConfigSerializer(
 | 
			
		||||
 | 
			
		||||
@ -1,37 +0,0 @@
 | 
			
		||||
"""core messages API"""
 | 
			
		||||
from django.contrib.messages import get_messages
 | 
			
		||||
from django.db.models import Model
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import ReadOnlyField, Serializer
 | 
			
		||||
from rest_framework.viewsets import ViewSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MessageSerializer(Serializer):
 | 
			
		||||
    """Serialize Django Message into DRF Object"""
 | 
			
		||||
 | 
			
		||||
    message = ReadOnlyField()
 | 
			
		||||
    level = ReadOnlyField()
 | 
			
		||||
    tags = ReadOnlyField()
 | 
			
		||||
    extra_tags = ReadOnlyField()
 | 
			
		||||
    level_tag = ReadOnlyField()
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def update(self, instance: Model, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MessagesViewSet(ViewSet):
 | 
			
		||||
    """Read-only view set that returns the current session's messages"""
 | 
			
		||||
 | 
			
		||||
    permission_classes = [AllowAny]
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(responses={200: MessageSerializer(many=True)})
 | 
			
		||||
    def list(self, request: Request) -> Response:
 | 
			
		||||
        """List current messages and pass into Serializer"""
 | 
			
		||||
        all_messages = list(get_messages(request))
 | 
			
		||||
        return Response(MessageSerializer(all_messages, many=True).data)
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
"""api v2 urls"""
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.urls import path, re_path
 | 
			
		||||
from drf_yasg2 import openapi
 | 
			
		||||
from drf_yasg2.views import get_schema_view
 | 
			
		||||
@ -10,7 +11,6 @@ from authentik.admin.api.tasks import TaskViewSet
 | 
			
		||||
from authentik.admin.api.version import VersionViewSet
 | 
			
		||||
from authentik.admin.api.workers import WorkerViewSet
 | 
			
		||||
from authentik.api.v2.config import ConfigsViewSet
 | 
			
		||||
from authentik.api.v2.messages import MessagesViewSet
 | 
			
		||||
from authentik.core.api.applications import ApplicationViewSet
 | 
			
		||||
from authentik.core.api.groups import GroupViewSet
 | 
			
		||||
from authentik.core.api.propertymappings import PropertyMappingViewSet
 | 
			
		||||
@ -55,12 +55,24 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide
 | 
			
		||||
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
 | 
			
		||||
from authentik.sources.oauth.api import OAuthSourceViewSet
 | 
			
		||||
from authentik.sources.saml.api import SAMLSourceViewSet
 | 
			
		||||
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet
 | 
			
		||||
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet
 | 
			
		||||
from authentik.stages.authenticator_static.api import (
 | 
			
		||||
    AuthenticatorStaticStageViewSet,
 | 
			
		||||
    StaticAdminDeviceViewSet,
 | 
			
		||||
    StaticDeviceViewSet,
 | 
			
		||||
)
 | 
			
		||||
from authentik.stages.authenticator_totp.api import (
 | 
			
		||||
    AuthenticatorTOTPStageViewSet,
 | 
			
		||||
    TOTPAdminDeviceViewSet,
 | 
			
		||||
    TOTPDeviceViewSet,
 | 
			
		||||
)
 | 
			
		||||
from authentik.stages.authenticator_validate.api import (
 | 
			
		||||
    AuthenticatorValidateStageViewSet,
 | 
			
		||||
)
 | 
			
		||||
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet
 | 
			
		||||
from authentik.stages.authenticator_webauthn.api import (
 | 
			
		||||
    AuthenticateWebAuthnStageViewSet,
 | 
			
		||||
    WebAuthnAdminDeviceViewSet,
 | 
			
		||||
    WebAuthnDeviceViewSet,
 | 
			
		||||
)
 | 
			
		||||
from authentik.stages.captcha.api import CaptchaStageViewSet
 | 
			
		||||
from authentik.stages.consent.api import ConsentStageViewSet
 | 
			
		||||
from authentik.stages.deny.api import DenyStageViewSet
 | 
			
		||||
@ -77,7 +89,6 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
 | 
			
		||||
 | 
			
		||||
router = routers.DefaultRouter()
 | 
			
		||||
 | 
			
		||||
router.register("root/messages", MessagesViewSet, basename="messages")
 | 
			
		||||
router.register("root/config", ConfigsViewSet, basename="configs")
 | 
			
		||||
 | 
			
		||||
router.register("admin/version", VersionViewSet, basename="admin_version")
 | 
			
		||||
@ -135,6 +146,13 @@ router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
 | 
			
		||||
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
 | 
			
		||||
router.register("propertymappings/scope", ScopeMappingViewSet)
 | 
			
		||||
 | 
			
		||||
router.register("authenticators/static", StaticDeviceViewSet)
 | 
			
		||||
router.register("authenticators/totp", TOTPDeviceViewSet)
 | 
			
		||||
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
 | 
			
		||||
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
 | 
			
		||||
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
 | 
			
		||||
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)
 | 
			
		||||
 | 
			
		||||
router.register("stages/all", StageViewSet)
 | 
			
		||||
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
 | 
			
		||||
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
 | 
			
		||||
@ -166,27 +184,26 @@ info = openapi.Info(
 | 
			
		||||
        name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
SchemaView = get_schema_view(
 | 
			
		||||
    info,
 | 
			
		||||
    public=True,
 | 
			
		||||
    permission_classes=(AllowAny,),
 | 
			
		||||
)
 | 
			
		||||
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    re_path(
 | 
			
		||||
        r"^swagger(?P<format>\.json|\.yaml)$",
 | 
			
		||||
        SchemaView.without_ui(cache_timeout=0),
 | 
			
		||||
        name="schema-json",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "swagger/",
 | 
			
		||||
        SchemaView.with_ui("swagger", cache_timeout=0),
 | 
			
		||||
        name="schema-swagger-ui",
 | 
			
		||||
    ),
 | 
			
		||||
    path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
 | 
			
		||||
urlpatterns = router.urls + [
 | 
			
		||||
    path(
 | 
			
		||||
        "flows/executor/<slug:flow_slug>/",
 | 
			
		||||
        FlowExecutorView.as_view(),
 | 
			
		||||
        name="flow-executor",
 | 
			
		||||
    ),
 | 
			
		||||
] + router.urls
 | 
			
		||||
    re_path(
 | 
			
		||||
        r"^swagger(?P<format>\.json|\.yaml)$",
 | 
			
		||||
        SchemaView.without_ui(cache_timeout=0),
 | 
			
		||||
        name="schema-json",
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
if settings.DEBUG:
 | 
			
		||||
    urlpatterns = urlpatterns + [
 | 
			
		||||
        path(
 | 
			
		||||
            "swagger/",
 | 
			
		||||
            SchemaView.with_ui("swagger", cache_timeout=0),
 | 
			
		||||
            name="schema-swagger-ui",
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
from django.http.response import Http404
 | 
			
		||||
from drf_yasg2.utils import swagger_auto_schema
 | 
			
		||||
from guardian.shortcuts import get_objects_for_user
 | 
			
		||||
from rest_framework.decorators import action
 | 
			
		||||
from rest_framework.fields import SerializerMethodField
 | 
			
		||||
@ -13,7 +14,7 @@ from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
from rest_framework_guardian.filters import ObjectPermissionsFilter
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.admin.api.metrics import get_events_per_1h
 | 
			
		||||
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
 | 
			
		||||
from authentik.core.api.providers import ProviderSerializer
 | 
			
		||||
from authentik.core.models import Application
 | 
			
		||||
from authentik.events.models import EventAction
 | 
			
		||||
@ -109,6 +110,7 @@ class ApplicationViewSet(ModelViewSet):
 | 
			
		||||
        serializer = self.get_serializer(allowed_applications, many=True)
 | 
			
		||||
        return self.get_paginated_response(serializer.data)
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
 | 
			
		||||
    @action(detail=True)
 | 
			
		||||
    def metrics(self, request: Request, slug: str):
 | 
			
		||||
        """Metrics for application logins"""
 | 
			
		||||
 | 
			
		||||
@ -21,3 +21,4 @@ class GroupViewSet(ModelViewSet):
 | 
			
		||||
    serializer_class = GroupSerializer
 | 
			
		||||
    search_fields = ["name", "is_superuser"]
 | 
			
		||||
    filterset_fields = ["name", "is_superuser"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
@ -28,9 +28,9 @@ class MetaNameSerializer(Serializer):
 | 
			
		||||
class TypeCreateSerializer(Serializer):
 | 
			
		||||
    """Types of an object that can be created"""
 | 
			
		||||
 | 
			
		||||
    name = CharField(read_only=True)
 | 
			
		||||
    description = CharField(read_only=True)
 | 
			
		||||
    link = CharField(read_only=True)
 | 
			
		||||
    name = CharField(required=True)
 | 
			
		||||
    description = CharField(required=True)
 | 
			
		||||
    link = CharField(required=True)
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data: dict) -> Model:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
@ -46,8 +46,7 @@ def backup_database(self: MonitoredTask):  # pragma: no cover
 | 
			
		||||
            TaskResult(
 | 
			
		||||
                TaskResultStatus.SUCCESSFUL,
 | 
			
		||||
                [
 | 
			
		||||
                    f"Successfully finished database backup {naturaltime(start)}",
 | 
			
		||||
                    out.getvalue(),
 | 
			
		||||
                    f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}",
 | 
			
		||||
                ],
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -11,9 +11,7 @@
 | 
			
		||||
        <title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
 | 
			
		||||
        <link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.css' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-addons.css' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="{% static 'dist/fontawesome.min.css' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}?v={{ ak_version }}">
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}">
 | 
			
		||||
        <script src="{% url 'javascript-catalog' %}?v={{ ak_version }}"></script>
 | 
			
		||||
        {% block head %}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
{% extends container_template|default:"administration/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load authentik_utils %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<section class="pf-c-page__main-section pf-m-light">
 | 
			
		||||
    <div class="pf-c-content">
 | 
			
		||||
        {% block above_form %}
 | 
			
		||||
@ -38,4 +35,3 @@
 | 
			
		||||
    <input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
 | 
			
		||||
    <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
 | 
			
		||||
</footer>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@
 | 
			
		||||
<div class="pf-c-background-image">
 | 
			
		||||
    <svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
 | 
			
		||||
        <filter id="image_overlay">
 | 
			
		||||
            <feColorMatrix type="matrix" values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0"></feColorMatrix>
 | 
			
		||||
            <feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
 | 
			
		||||
            <feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
 | 
			
		||||
                <feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
 | 
			
		||||
                <feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
 | 
			
		||||
@ -48,7 +48,7 @@
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    {% if config.authentik.branding.title != "authentik" %}
 | 
			
		||||
                    <li>
 | 
			
		||||
                        <a href="https://github.com/beryju/authentik">
 | 
			
		||||
                        <a href="https://goauthentik.io">
 | 
			
		||||
                            {% trans 'Powered by authentik' %}
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
                <p>{% trans "Configure settings relevant to your user profile." %}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </section>
 | 
			
		||||
        <ak-tabs>
 | 
			
		||||
        <ak-tabs vertical="true" style="height: 100%;">
 | 
			
		||||
            <section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
 | 
			
		||||
                <div class="pf-u-display-flex pf-u-justify-content-center">
 | 
			
		||||
                    <div class="pf-u-w-75">
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ from django.contrib.auth.mixins import (
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http.response import HttpResponse
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.views.generic import UpdateView
 | 
			
		||||
from django.views.generic.base import TemplateView
 | 
			
		||||
@ -34,7 +35,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    form_class = UserDetailForm
 | 
			
		||||
 | 
			
		||||
    success_message = _("Successfully updated user.")
 | 
			
		||||
    success_url = "/"
 | 
			
		||||
    success_url = reverse_lazy("authentik_core:user-details")
 | 
			
		||||
 | 
			
		||||
    def get_object(self):
 | 
			
		||||
        return self.request.user
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@ class EventSerializer(ModelSerializer):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventTopPerUserSerialier(Serializer):
 | 
			
		||||
class EventTopPerUserSerializer(Serializer):
 | 
			
		||||
    """Response object of Event's top_per_user"""
 | 
			
		||||
 | 
			
		||||
    application = DictField()
 | 
			
		||||
@ -60,7 +60,7 @@ class EventViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
    filterset_fields = ["action"]
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(
 | 
			
		||||
        method="GET", responses={200: EventTopPerUserSerialier(many=True)}
 | 
			
		||||
        method="GET", responses={200: EventTopPerUserSerializer(many=True)}
 | 
			
		||||
    )
 | 
			
		||||
    @action(detail=False, methods=["GET"])
 | 
			
		||||
    def top_per_user(self, request: Request):
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    body = ReadOnlyField()
 | 
			
		||||
    severity = ReadOnlyField()
 | 
			
		||||
    event = EventSerializer()
 | 
			
		||||
    event = EventSerializer(required=False)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,12 @@
 | 
			
		||||
"""NotificationTransport API Views"""
 | 
			
		||||
from django.http.response import Http404
 | 
			
		||||
from drf_yasg2.utils import no_body, swagger_auto_schema
 | 
			
		||||
from guardian.shortcuts import get_objects_for_user
 | 
			
		||||
from rest_framework.decorators import action
 | 
			
		||||
from rest_framework.fields import SerializerMethodField
 | 
			
		||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.serializers import ModelSerializer, Serializer
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.events.models import (
 | 
			
		||||
@ -38,12 +39,28 @@ class NotificationTransportSerializer(ModelSerializer):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotificationTransportTestSerializer(Serializer):
 | 
			
		||||
    """Notification test serializer"""
 | 
			
		||||
 | 
			
		||||
    messages = ListField(child=CharField())
 | 
			
		||||
 | 
			
		||||
    def create(self, request: Request) -> Response:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def update(self, request: Request) -> Response:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotificationTransportViewSet(ModelViewSet):
 | 
			
		||||
    """NotificationTransport Viewset"""
 | 
			
		||||
 | 
			
		||||
    queryset = NotificationTransport.objects.all()
 | 
			
		||||
    serializer_class = NotificationTransportSerializer
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(
 | 
			
		||||
        responses={200: NotificationTransportTestSerializer(many=False)},
 | 
			
		||||
        request_body=no_body,
 | 
			
		||||
    )
 | 
			
		||||
    @action(detail=True, methods=["post"])
 | 
			
		||||
    # pylint: disable=invalid-name
 | 
			
		||||
    def test(self, request: Request, pk=None) -> Response:
 | 
			
		||||
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
 | 
			
		||||
            user=request.user,
 | 
			
		||||
        )
 | 
			
		||||
        try:
 | 
			
		||||
            return Response(transport.send(notification))
 | 
			
		||||
            response = NotificationTransportTestSerializer(
 | 
			
		||||
                data={"messages": transport.send(notification)}
 | 
			
		||||
            )
 | 
			
		||||
            response.is_valid()
 | 
			
		||||
            return Response(response.data)
 | 
			
		||||
        except NotificationTransportError as exc:
 | 
			
		||||
            return Response(str(exc.__cause__ or None), status=503)
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,10 @@ def get_geoip_reader() -> Optional[Reader]:
 | 
			
		||||
    path = CONFIG.y("authentik.geoip")
 | 
			
		||||
    if path == "" or not path:
 | 
			
		||||
        return None
 | 
			
		||||
    return Reader(path)
 | 
			
		||||
    try:
 | 
			
		||||
        return Reader(path)
 | 
			
		||||
    except OSError:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
GEOIP_READER = get_geoip_reader()
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,9 @@ class Challenge(Serializer):
 | 
			
		||||
    """Challenge that gets sent to the client based on which stage
 | 
			
		||||
    is currently active"""
 | 
			
		||||
 | 
			
		||||
    type = ChoiceField(choices=list(ChallengeTypes))
 | 
			
		||||
    type = ChoiceField(
 | 
			
		||||
        choices=[(x.name, x.name) for x in ChallengeTypes],
 | 
			
		||||
    )
 | 
			
		||||
    component = CharField(required=False)
 | 
			
		||||
    title = CharField(required=False)
 | 
			
		||||
 | 
			
		||||
@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):
 | 
			
		||||
 | 
			
		||||
    stage: Optional["StageView"]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, instance, data, **kwargs):
 | 
			
		||||
    def __init__(self, instance=None, data=None, **kwargs):
 | 
			
		||||
        self.stage = kwargs.pop("stage", None)
 | 
			
		||||
        super().__init__(instance=instance, data=data, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ from django.http import HttpRequest
 | 
			
		||||
from django.http.request import QueryDict
 | 
			
		||||
from django.http.response import HttpResponse
 | 
			
		||||
from django.views.generic.base import View
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import DEFAULT_AVATAR, User
 | 
			
		||||
@ -67,9 +68,9 @@ class ChallengeStageView(StageView):
 | 
			
		||||
        return HttpChallengeResponse(challenge)
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
    def post(self, request: Request, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
        """Handle challenge response"""
 | 
			
		||||
        challenge: ChallengeResponse = self.get_response_instance(data=request.POST)
 | 
			
		||||
        challenge: ChallengeResponse = self.get_response_instance(data=request.data)
 | 
			
		||||
        if not challenge.is_valid():
 | 
			
		||||
            return self.challenge_invalid(challenge)
 | 
			
		||||
        return self.challenge_valid(challenge)
 | 
			
		||||
 | 
			
		||||
@ -9,11 +9,16 @@ from django.template.response import TemplateResponse
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
 | 
			
		||||
from django.views.generic import TemplateView, View
 | 
			
		||||
from drf_yasg2.utils import no_body, swagger_auto_schema
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
from structlog.stdlib import BoundLogger, get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
 | 
			
		||||
from authentik.events.models import cleanse_dict
 | 
			
		||||
from authentik.flows.challenge import (
 | 
			
		||||
    Challenge,
 | 
			
		||||
    ChallengeResponse,
 | 
			
		||||
    ChallengeTypes,
 | 
			
		||||
    HttpChallengeResponse,
 | 
			
		||||
    RedirectChallenge,
 | 
			
		||||
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(xframe_options_sameorigin, name="dispatch")
 | 
			
		||||
class FlowExecutorView(View):
 | 
			
		||||
class FlowExecutorView(APIView):
 | 
			
		||||
    """Stage 1 Flow executor, passing requests to Stage Views"""
 | 
			
		||||
 | 
			
		||||
    permission_classes = [AllowAny]
 | 
			
		||||
 | 
			
		||||
    flow: Flow
 | 
			
		||||
 | 
			
		||||
    plan: Optional[FlowPlan] = None
 | 
			
		||||
@ -113,8 +120,13 @@ class FlowExecutorView(View):
 | 
			
		||||
        self.current_stage_view.request = request
 | 
			
		||||
        return super().dispatch(request)
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(
 | 
			
		||||
        responses={200: Challenge()},
 | 
			
		||||
        request_body=no_body,
 | 
			
		||||
        operation_id="flows_executor_get",
 | 
			
		||||
    )
 | 
			
		||||
    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
        """pass get request to current stage"""
 | 
			
		||||
        """Get the next pending challenge from the currently active flow."""
 | 
			
		||||
        self._logger.debug(
 | 
			
		||||
            "f(exec): Passing GET",
 | 
			
		||||
            view_class=class_to_path(self.current_stage_view.__class__),
 | 
			
		||||
@ -127,8 +139,13 @@ class FlowExecutorView(View):
 | 
			
		||||
            self._logger.exception(exc)
 | 
			
		||||
            return to_stage_response(request, FlowErrorResponse(request, exc))
 | 
			
		||||
 | 
			
		||||
    @swagger_auto_schema(
 | 
			
		||||
        responses={200: Challenge()},
 | 
			
		||||
        request_body=ChallengeResponse(),
 | 
			
		||||
        operation_id="flows_executor_solve",
 | 
			
		||||
    )
 | 
			
		||||
    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
        """pass post request to current stage"""
 | 
			
		||||
        """Solve the previously retrieved challenge and advanced to the next stage."""
 | 
			
		||||
        self._logger.debug(
 | 
			
		||||
            "f(exec): Passing POST",
 | 
			
		||||
            view_class=class_to_path(self.current_stage_view.__class__),
 | 
			
		||||
@ -175,8 +192,10 @@ class FlowExecutorView(View):
 | 
			
		||||
                "f(exec): Continuing with next stage",
 | 
			
		||||
                reamining=len(self.plan.stages),
 | 
			
		||||
            )
 | 
			
		||||
            kwargs = self.kwargs
 | 
			
		||||
            kwargs.update({"flow_slug": self.flow.slug})
 | 
			
		||||
            return redirect_with_qs(
 | 
			
		||||
                "authentik_api:flow-executor", self.request.GET, **self.kwargs
 | 
			
		||||
                "authentik_api:flow-executor", self.request.GET, **kwargs
 | 
			
		||||
            )
 | 
			
		||||
        # User passed all stages
 | 
			
		||||
        self._logger.debug(
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ redis:
 | 
			
		||||
  ws_db: 2
 | 
			
		||||
 | 
			
		||||
debug: false
 | 
			
		||||
 | 
			
		||||
log_level: info
 | 
			
		||||
 | 
			
		||||
# Error reporting, sends stacktrace to sentry.beryju.org
 | 
			
		||||
 | 
			
		||||
@ -55,13 +55,21 @@ class OutpostConsumer(AuthJsonConsumer):
 | 
			
		||||
        OutpostState(
 | 
			
		||||
            uid=self.channel_name, last_seen=datetime.now(), _outpost=self.outpost
 | 
			
		||||
        ).save(timeout=OUTPOST_HELLO_INTERVAL * 1.5)
 | 
			
		||||
        LOGGER.debug("added channel to cache", channel_name=self.channel_name)
 | 
			
		||||
        LOGGER.debug(
 | 
			
		||||
            "added outpost instace to cache",
 | 
			
		||||
            outpost=self.outpost,
 | 
			
		||||
            channel_name=self.channel_name,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def disconnect(self, close_code):
 | 
			
		||||
        if self.outpost:
 | 
			
		||||
            OutpostState.for_channel(self.outpost, self.channel_name).delete()
 | 
			
		||||
        LOGGER.debug("removed channel from cache", channel_name=self.channel_name)
 | 
			
		||||
        LOGGER.debug(
 | 
			
		||||
            "removed outpost instance from cache",
 | 
			
		||||
            outpost=self.outpost,
 | 
			
		||||
            channel_name=self.channel_name,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def receive_json(self, content: Data):
 | 
			
		||||
        msg = from_dict(WebsocketMessage, content)
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ from typing import Type
 | 
			
		||||
from kubernetes.client import OpenApiException
 | 
			
		||||
from kubernetes.client.api_client import ApiClient
 | 
			
		||||
from structlog.testing import capture_logs
 | 
			
		||||
from urllib3.exceptions import HTTPError
 | 
			
		||||
from yaml import dump_all
 | 
			
		||||
 | 
			
		||||
from authentik.outposts.controllers.base import BaseController, ControllerException
 | 
			
		||||
@ -42,7 +43,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                reconciler = self.reconcilers[reconcile_key](self)
 | 
			
		||||
                reconciler.up()
 | 
			
		||||
 | 
			
		||||
        except OpenApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError) as exc:
 | 
			
		||||
            raise ControllerException from exc
 | 
			
		||||
 | 
			
		||||
    def up_with_logs(self) -> list[str]:
 | 
			
		||||
@ -54,7 +55,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                    reconciler.up()
 | 
			
		||||
                all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
 | 
			
		||||
            return all_logs
 | 
			
		||||
        except OpenApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError) as exc:
 | 
			
		||||
            raise ControllerException from exc
 | 
			
		||||
 | 
			
		||||
    def down(self):
 | 
			
		||||
 | 
			
		||||
@ -26,5 +26,5 @@ def invalidate_policy_cache(sender, instance, **_):
 | 
			
		||||
            cache.delete_many(keys)
 | 
			
		||||
        LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
 | 
			
		||||
    # Also delete user application cache
 | 
			
		||||
    keys = cache.keys(user_app_cache_key("*"))
 | 
			
		||||
    keys = cache.keys(user_app_cache_key("*")) or []
 | 
			
		||||
    cache.delete_many(keys)
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,9 @@ def protected_resource_view(scopes: list[str]):
 | 
			
		||||
    This decorator also injects the token into `kwargs`"""
 | 
			
		||||
 | 
			
		||||
    def wrapper(view):
 | 
			
		||||
        def view_wrapper(request, *args, **kwargs):
 | 
			
		||||
        def view_wrapper(request: HttpRequest, *args, **kwargs):
 | 
			
		||||
            if request.method == "OPTIONS":
 | 
			
		||||
                return view(request, *args, **kwargs)
 | 
			
		||||
            try:
 | 
			
		||||
                access_token = extract_access_token(request)
 | 
			
		||||
                if not access_token:
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ from authentik.providers.oauth2.models import (
 | 
			
		||||
    ResponseTypes,
 | 
			
		||||
    ScopeMapping,
 | 
			
		||||
)
 | 
			
		||||
from authentik.providers.oauth2.utils import cors_allow_any
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
@ -103,9 +104,10 @@ class ProviderInfoView(View):
 | 
			
		||||
        provider: OAuth2Provider = get_object_or_404(
 | 
			
		||||
            OAuth2Provider, pk=application.provider_id
 | 
			
		||||
        )
 | 
			
		||||
        response = JsonResponse(
 | 
			
		||||
            self.get_info(provider), json_dumps_params={"indent": 2}
 | 
			
		||||
        )
 | 
			
		||||
        response["Access-Control-Allow-Origin"] = "*"
 | 
			
		||||
        return JsonResponse(self.get_info(provider), json_dumps_params={"indent": 2})
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
 | 
			
		||||
        # Since this view only supports get, we can statically set the CORS headers
 | 
			
		||||
        response = super().dispatch(request, *args, **kwargs)
 | 
			
		||||
        cors_allow_any(request, response)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
@ -74,7 +74,7 @@ class SAMLFlowFinalView(ChallengeStageView):
 | 
			
		||||
            return super().get(
 | 
			
		||||
                self.request,
 | 
			
		||||
                **{
 | 
			
		||||
                    "type": ChallengeTypes.native,
 | 
			
		||||
                    "type": ChallengeTypes.native.value,
 | 
			
		||||
                    "component": "ak-stage-autosubmit",
 | 
			
		||||
                    "title": "Redirecting to %(app)s..." % {"app": application.name},
 | 
			
		||||
                    "url": provider.acs_url,
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ from sentry_sdk.integrations.celery import CeleryIntegration
 | 
			
		||||
from sentry_sdk.integrations.django import DjangoIntegration
 | 
			
		||||
from sentry_sdk.integrations.redis import RedisIntegration
 | 
			
		||||
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik import ENV_GIT_HASH_KEY, __version__
 | 
			
		||||
from authentik.core.middleware import structlog_add_request_id
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
from authentik.lib.logging import add_process_id
 | 
			
		||||
@ -139,6 +139,9 @@ GUARDIAN_MONKEY_PATCH = False
 | 
			
		||||
 | 
			
		||||
SWAGGER_SETTINGS = {
 | 
			
		||||
    "DEFAULT_INFO": "authentik.api.v2.urls.info",
 | 
			
		||||
    "DEFAULT_PAGINATOR_INSPECTORS": [
 | 
			
		||||
        "authentik.api.pagination_schema.PaginationInspector",
 | 
			
		||||
    ],
 | 
			
		||||
    "SECURITY_DEFINITIONS": {
 | 
			
		||||
        "token": {"type": "apiKey", "name": "Authorization", "in": "header"}
 | 
			
		||||
    },
 | 
			
		||||
@ -147,7 +150,6 @@ SWAGGER_SETTINGS = {
 | 
			
		||||
REST_FRAMEWORK = {
 | 
			
		||||
    "DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
 | 
			
		||||
    "PAGE_SIZE": 100,
 | 
			
		||||
    "DATETIME_FORMAT": "%s",
 | 
			
		||||
    "DEFAULT_FILTER_BACKENDS": [
 | 
			
		||||
        "rest_framework_guardian.filters.ObjectPermissionsFilter",
 | 
			
		||||
        "django_filters.rest_framework.DjangoFilterBackend",
 | 
			
		||||
@ -472,6 +474,7 @@ for _app in INSTALLED_APPS:
 | 
			
		||||
 | 
			
		||||
if DEBUG:
 | 
			
		||||
    CELERY_TASK_ALWAYS_EAGER = True
 | 
			
		||||
    os.environ[ENV_GIT_HASH_KEY] = "dev"
 | 
			
		||||
 | 
			
		||||
INSTALLED_APPS.append("authentik.core.apps.AuthentikCoreConfig")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -60,10 +60,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
 | 
			
		||||
                    "Synced User", user=ak_user.username, created=created
 | 
			
		||||
                )
 | 
			
		||||
                user_count += 1
 | 
			
		||||
                # pylint: disable=no-value-for-parameter
 | 
			
		||||
                pwd_last_set = UTC.localize(
 | 
			
		||||
                    attributes.get("pwdLastSet", datetime.now())
 | 
			
		||||
                )
 | 
			
		||||
                pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
 | 
			
		||||
                pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
 | 
			
		||||
                if created or pwd_last_set >= ak_user.password_change_date:
 | 
			
		||||
                    self._logger.debug(
 | 
			
		||||
                        "Reset user's password",
 | 
			
		||||
 | 
			
		||||
@ -15,9 +15,11 @@ class OAuthSourceForm(forms.ModelForm):
 | 
			
		||||
        self.fields["authentication_flow"].queryset = Flow.objects.filter(
 | 
			
		||||
            designation=FlowDesignation.AUTHENTICATION
 | 
			
		||||
        )
 | 
			
		||||
        self.fields["authentication_flow"].required = True
 | 
			
		||||
        self.fields["enrollment_flow"].queryset = Flow.objects.filter(
 | 
			
		||||
            designation=FlowDesignation.ENROLLMENT
 | 
			
		||||
        )
 | 
			
		||||
        self.fields["enrollment_flow"].required = True
 | 
			
		||||
        if hasattr(self.Meta, "overrides"):
 | 
			
		||||
            for overide_field, overide_value in getattr(self.Meta, "overrides").items():
 | 
			
		||||
                self.fields[overide_field].initial = overide_value
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ from typing import Any, Optional
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.http import Http404, HttpRequest, HttpResponse
 | 
			
		||||
from django.http.response import HttpResponseBadRequest
 | 
			
		||||
from django.shortcuts import redirect
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
@ -151,6 +152,8 @@ class OAuthCallback(OAuthClientMixin, View):
 | 
			
		||||
                PLAN_CONTEXT_REDIRECT: final_redirect,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        if not flow:
 | 
			
		||||
            return HttpResponseBadRequest()
 | 
			
		||||
        # We run the Flow planner here so we can pass the Pending user in the context
 | 
			
		||||
        planner = FlowPlanner(flow)
 | 
			
		||||
        plan = planner.plan(self.request, kwargs)
 | 
			
		||||
@ -233,6 +236,9 @@ class OAuthCallback(OAuthClientMixin, View):
 | 
			
		||||
            PLAN_CONTEXT_SOURCES_OAUTH_ACCESS: access,
 | 
			
		||||
        }
 | 
			
		||||
        # We run the Flow planner here so we can pass the Pending user in the context
 | 
			
		||||
        if not source.enrollment_flow:
 | 
			
		||||
            LOGGER.warning("source has no enrollment flow", source=source)
 | 
			
		||||
            return HttpResponseBadRequest()
 | 
			
		||||
        planner = FlowPlanner(source.enrollment_flow)
 | 
			
		||||
        plan = planner.plan(self.request, context)
 | 
			
		||||
        plan.append(in_memory_stage(PostUserEnrollmentStage))
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
"""AuthenticatorStaticStage API Views"""
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
from django_otp.plugins.otp_static.models import StaticDevice
 | 
			
		||||
from rest_framework.permissions import IsAdminUser
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.flows.api.stages import StageSerializer
 | 
			
		||||
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
 | 
			
		||||
@ -19,3 +22,39 @@ class AuthenticatorStaticStageViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
    queryset = AuthenticatorStaticStage.objects.all()
 | 
			
		||||
    serializer_class = AuthenticatorStaticStageSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StaticDeviceSerializer(ModelSerializer):
 | 
			
		||||
    """Serializer for static authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = StaticDevice
 | 
			
		||||
        fields = ["name", "token_set"]
 | 
			
		||||
        depth = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StaticDeviceViewSet(ModelViewSet):
 | 
			
		||||
    """Viewset for static authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    queryset = StaticDevice.objects.none()
 | 
			
		||||
    serializer_class = StaticDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if not self.request:
 | 
			
		||||
            return super().get_queryset()
 | 
			
		||||
        return StaticDevice.objects.filter(user=self.request.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StaticAdminDeviceViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
    """Viewset for static authenticator devices (for admins)"""
 | 
			
		||||
 | 
			
		||||
    permission_classes = [IsAdminUser]
 | 
			
		||||
    queryset = StaticDevice.objects.all()
 | 
			
		||||
    serializer_class = StaticDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
 | 
			
		||||
        tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
 | 
			
		||||
        return AuthenticatorStaticChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-authenticator-static",
 | 
			
		||||
                "codes": [token.token for token in tokens],
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,8 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
class DisableView(LoginRequiredMixin, View):
 | 
			
		||||
    """Disable Static Tokens for user"""
 | 
			
		||||
 | 
			
		||||
    def get(self, request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def get(self, request: HttpRequest, **kwargs) -> HttpResponse:
 | 
			
		||||
        """Delete all the devices for user"""
 | 
			
		||||
        devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
 | 
			
		||||
        devices.delete()
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
"""AuthenticatorTOTPStage API Views"""
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
from django_otp.plugins.otp_totp.models import TOTPDevice
 | 
			
		||||
from rest_framework.permissions import IsAdminUser
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.flows.api.stages import StageSerializer
 | 
			
		||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
 | 
			
		||||
@ -19,3 +22,41 @@ class AuthenticatorTOTPStageViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
    queryset = AuthenticatorTOTPStage.objects.all()
 | 
			
		||||
    serializer_class = AuthenticatorTOTPStageSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TOTPDeviceSerializer(ModelSerializer):
 | 
			
		||||
    """Serializer for totp authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = TOTPDevice
 | 
			
		||||
        fields = [
 | 
			
		||||
            "name",
 | 
			
		||||
        ]
 | 
			
		||||
        depth = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TOTPDeviceViewSet(ModelViewSet):
 | 
			
		||||
    """Viewset for totp authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    queryset = TOTPDevice.objects.none()
 | 
			
		||||
    serializer_class = TOTPDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if not self.request:
 | 
			
		||||
            return super().get_queryset()
 | 
			
		||||
        return TOTPDevice.objects.filter(user=self.request.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
    """Viewset for totp authenticator devices (for admins)"""
 | 
			
		||||
 | 
			
		||||
    permission_classes = [IsAdminUser]
 | 
			
		||||
    queryset = TOTPDevice.objects.all()
 | 
			
		||||
    serializer_class = TOTPDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
 | 
			
		||||
        device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
 | 
			
		||||
        return AuthenticatorTOTPChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-authenticator-totp",
 | 
			
		||||
                "config_url": device.config_url,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
"""OTP Validate stage forms"""
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django_otp import match_token
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.models import NotConfiguredAction
 | 
			
		||||
from authentik.stages.authenticator_validate.models import (
 | 
			
		||||
    AuthenticatorValidateStage,
 | 
			
		||||
@ -11,35 +8,6 @@ from authentik.stages.authenticator_validate.models import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ValidationForm(forms.Form):
 | 
			
		||||
    """OTP Validate stage forms"""
 | 
			
		||||
 | 
			
		||||
    user: User
 | 
			
		||||
 | 
			
		||||
    code = forms.CharField(
 | 
			
		||||
        label=_("Please enter the token from your device."),
 | 
			
		||||
        widget=forms.TextInput(
 | 
			
		||||
            attrs={
 | 
			
		||||
                "autocomplete": "one-time-code",
 | 
			
		||||
                "placeholder": "123456",
 | 
			
		||||
                "autofocus": "autofocus",
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, user, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.user = user
 | 
			
		||||
 | 
			
		||||
    def clean_code(self):
 | 
			
		||||
        """Validate code against all confirmed devices"""
 | 
			
		||||
        code = self.cleaned_data.get("code")
 | 
			
		||||
        device = match_token(self.user, code)
 | 
			
		||||
        if not device:
 | 
			
		||||
            raise forms.ValidationError(_("Invalid Token"))
 | 
			
		||||
        return code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthenticatorValidateStageForm(forms.ModelForm):
 | 
			
		||||
    """OTP Validate stage forms"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -145,7 +145,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
 | 
			
		||||
        challenges = self.request.session["device_challenges"]
 | 
			
		||||
        return AuthenticatorChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-authenticator-validate",
 | 
			
		||||
                "device_challenges": challenges,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,13 @@
 | 
			
		||||
"""AuthenticateWebAuthnStage API Views"""
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet
 | 
			
		||||
from rest_framework.permissions import IsAdminUser
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
 | 
			
		||||
 | 
			
		||||
from authentik.flows.api.stages import StageSerializer
 | 
			
		||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
 | 
			
		||||
from authentik.stages.authenticator_webauthn.models import (
 | 
			
		||||
    AuthenticateWebAuthnStage,
 | 
			
		||||
    WebAuthnDevice,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthenticateWebAuthnStageSerializer(StageSerializer):
 | 
			
		||||
@ -19,3 +24,41 @@ class AuthenticateWebAuthnStageViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
    queryset = AuthenticateWebAuthnStage.objects.all()
 | 
			
		||||
    serializer_class = AuthenticateWebAuthnStageSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WebAuthnDeviceSerializer(ModelSerializer):
 | 
			
		||||
    """Serializer for WebAuthn authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = WebAuthnDevice
 | 
			
		||||
        fields = [
 | 
			
		||||
            "name",
 | 
			
		||||
        ]
 | 
			
		||||
        depth = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WebAuthnDeviceViewSet(ModelViewSet):
 | 
			
		||||
    """Viewset for WebAuthn authenticator devices"""
 | 
			
		||||
 | 
			
		||||
    queryset = WebAuthnDevice.objects.none()
 | 
			
		||||
    serializer_class = WebAuthnDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        if not self.request:
 | 
			
		||||
            return super().get_queryset()
 | 
			
		||||
        return WebAuthnDevice.objects.filter(user=self.request.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
    """Viewset for WebAuthn authenticator devices (for admins)"""
 | 
			
		||||
 | 
			
		||||
    permission_classes = [IsAdminUser]
 | 
			
		||||
    queryset = WebAuthnDevice.objects.all()
 | 
			
		||||
    serializer_class = WebAuthnDeviceSerializer
 | 
			
		||||
    search_fields = ["name"]
 | 
			
		||||
    filterset_fields = ["name"]
 | 
			
		||||
    ordering = ["name"]
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
# Generated by Django 3.1.7 on 2021-03-04 18:50
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_stages_authenticator_webauthn", "0003_webauthndevice_confirmed"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name="webauthndevice",
 | 
			
		||||
            options={
 | 
			
		||||
                "verbose_name": "WebAuthn Device",
 | 
			
		||||
                "verbose_name_plural": "WebAuthn Devices",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -122,7 +122,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
 | 
			
		||||
 | 
			
		||||
        return AuthenticatorWebAuthnChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-authenticator-webauthn",
 | 
			
		||||
                "registration": make_credential_options.registration_dict,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,7 @@ class CaptchaStageView(ChallengeStageView):
 | 
			
		||||
    def get_challenge(self, *args, **kwargs) -> Challenge:
 | 
			
		||||
        return CaptchaChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-captcha",
 | 
			
		||||
                "site_key": self.executor.current_stage.public_key,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ class ConsentStageView(ChallengeStageView):
 | 
			
		||||
    def get_challenge(self) -> Challenge:
 | 
			
		||||
        challenge = ConsentChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-consent",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ class DummyStageView(ChallengeStageView):
 | 
			
		||||
    def get_challenge(self, *args, **kwargs) -> Challenge:
 | 
			
		||||
        return DummyChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "",
 | 
			
		||||
                "title": self.executor.current_stage.name,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,7 @@ class EmailStageView(ChallengeStageView):
 | 
			
		||||
 | 
			
		||||
    def get_challenge(self) -> Challenge:
 | 
			
		||||
        challenge = EmailChallenge(
 | 
			
		||||
            data={"type": ChallengeTypes.native, "component": "ak-stage-email"}
 | 
			
		||||
            data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
 | 
			
		||||
        )
 | 
			
		||||
        return challenge
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ class IdentificationStageView(ChallengeStageView):
 | 
			
		||||
        current_stage: IdentificationStage = self.executor.current_stage
 | 
			
		||||
        challenge = IdentificationChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-identification",
 | 
			
		||||
                "primary_action": _("Log in"),
 | 
			
		||||
                "input_type": "text",
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ class PasswordStageView(ChallengeStageView):
 | 
			
		||||
    def get_challenge(self) -> Challenge:
 | 
			
		||||
        challenge = PasswordChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-password",
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -164,7 +164,7 @@ class PromptStageView(ChallengeStageView):
 | 
			
		||||
        fields = list(self.executor.current_stage.fields.all().order_by("order"))
 | 
			
		||||
        challenge = PromptChallenge(
 | 
			
		||||
            data={
 | 
			
		||||
                "type": ChallengeTypes.native,
 | 
			
		||||
                "type": ChallengeTypes.native.value,
 | 
			
		||||
                "component": "ak-stage-prompt",
 | 
			
		||||
                "fields": [PromptSerializer(field).data for field in fields],
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
@ -279,6 +279,7 @@ stages:
 | 
			
		||||
            displayName: Build static files for e2e
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
 | 
			
		||||
                cd web
 | 
			
		||||
                npm i
 | 
			
		||||
                npm run build
 | 
			
		||||
@ -378,8 +379,15 @@ stages:
 | 
			
		||||
              python ./scripts/az_do_set_branch.py
 | 
			
		||||
        - task: Docker@2
 | 
			
		||||
          inputs:
 | 
			
		||||
            containerRegistry: 'GHCR'
 | 
			
		||||
            repository: 'beryju/authentik'
 | 
			
		||||
            command: 'buildAndPush'
 | 
			
		||||
            containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
            repository: 'authentik/server'
 | 
			
		||||
            command: 'build'
 | 
			
		||||
            Dockerfile: 'Dockerfile'
 | 
			
		||||
            tags: "gh-$(branchName)"
 | 
			
		||||
            tags: 'gh-$(branchName)'
 | 
			
		||||
            arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
 | 
			
		||||
        - task: Docker@2
 | 
			
		||||
          inputs:
 | 
			
		||||
            containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
            repository: 'authentik/server'
 | 
			
		||||
            command: 'push'
 | 
			
		||||
            tags: 'gh-$(branchName)'
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ services:
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
  server:
 | 
			
		||||
    image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.1-rc2}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.4}
 | 
			
		||||
    command: server
 | 
			
		||||
    environment:
 | 
			
		||||
      AUTHENTIK_REDIS__HOST: redis
 | 
			
		||||
@ -27,9 +27,11 @@ services:
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
 | 
			
		||||
      # AUTHENTIK_ERROR_REPORTING__ENABLED: true
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./media:/media
 | 
			
		||||
      - ./custom-templates:/templates
 | 
			
		||||
      - geoip:/geoip
 | 
			
		||||
    ports:
 | 
			
		||||
      - 8000
 | 
			
		||||
    networks:
 | 
			
		||||
@ -45,7 +47,7 @@ services:
 | 
			
		||||
    env_file:
 | 
			
		||||
      - .env
 | 
			
		||||
  worker:
 | 
			
		||||
    image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.1-rc2}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.4}
 | 
			
		||||
    command: worker
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
@ -55,14 +57,16 @@ services:
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
 | 
			
		||||
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
 | 
			
		||||
      # AUTHENTIK_ERROR_REPORTING__ENABLED: true
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./backups:/backups
 | 
			
		||||
      - /var/run/docker.sock:/var/run/docker.sock
 | 
			
		||||
      - ./custom-templates:/templates
 | 
			
		||||
      - geoip:/geoip
 | 
			
		||||
    env_file:
 | 
			
		||||
      - .env
 | 
			
		||||
  static:
 | 
			
		||||
    image: beryju/authentik-static:${AUTHENTIK_TAG:-2021.3.1-rc2}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.3.4}
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    labels:
 | 
			
		||||
@ -91,10 +95,21 @@ services:
 | 
			
		||||
      - "127.0.0.1:8080:8080"
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
  geoipupdate:
 | 
			
		||||
    image: "maxmindinc/geoipupdate:latest"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - "geoip:/usr/share/GeoIP"
 | 
			
		||||
    environment:
 | 
			
		||||
      GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
 | 
			
		||||
      GEOIPUPDATE_FREQUENCY: "8"
 | 
			
		||||
    env_file:
 | 
			
		||||
      - .env
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  database:
 | 
			
		||||
    driver: local
 | 
			
		||||
  geoip:
 | 
			
		||||
    driver: local
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  internal: {}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ name: authentik
 | 
			
		||||
home: https://goauthentik.io
 | 
			
		||||
sources:
 | 
			
		||||
  - https://github.com/BeryJu/authentik
 | 
			
		||||
version: "2021.3.1-rc2"
 | 
			
		||||
version: "2021.3.4"
 | 
			
		||||
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
 | 
			
		||||
dependencies:
 | 
			
		||||
  - name: postgresql
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@
 | 
			
		||||
|-----------------------------------|-------------------------|-------------|
 | 
			
		||||
| image.name                        | beryju/authentik        | Image used to run the authentik server and worker |
 | 
			
		||||
| image.name_static                 | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
 | 
			
		||||
| image.tag                         | 2021.3.1-rc2           | Image tag |
 | 
			
		||||
| image.tag                         | 2021.3.4                | Image tag |
 | 
			
		||||
| image.pullPolicy                  | IfNotPresent            | Image Pull Policy used for all deployments |
 | 
			
		||||
| serverReplicas                    | 1                       | Replicas for the Server deployment |
 | 
			
		||||
| workerReplicas                    | 1                       | Replicas for the Worker deployment |
 | 
			
		||||
@ -22,6 +22,10 @@
 | 
			
		||||
| config.email.use_ssl              | false                   | Enable SSL |
 | 
			
		||||
| config.email.timeout              | 10                      | SMTP Timeout |
 | 
			
		||||
| config.email.from                 | authentik@localhost     | Email address authentik will send from, should have a correct @domain |
 | 
			
		||||
| geoip.enabled                     | false                   | Optionally enable GeoIP |
 | 
			
		||||
| geoip.accountId                   |                         | GeoIP MaxMind Account ID |
 | 
			
		||||
| geoip.licenseKey                  |                         | GeoIP MaxMind License key |
 | 
			
		||||
| geoip.image                       | maxmindinc/geoipupdate:latest  | GeoIP Updater image |
 | 
			
		||||
| backup.accessKey                  |                         | Optionally enable S3 Backup, Access Key |
 | 
			
		||||
| backup.secretKey                  |                         | Optionally enable S3 Backup, Secret Key |
 | 
			
		||||
| backup.bucket                     |                         | Optionally enable S3 Backup, Bucket |
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								helm/templates/geoip-configmap.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								helm/templates/geoip-configmap.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
{{- if .Values.geoip.enabled -}}
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ConfigMap
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-geoip-config
 | 
			
		||||
data:
 | 
			
		||||
  GEOIPUPDATE_ACCOUNT_ID: "{{ .Values.geoip.accountId }}"
 | 
			
		||||
  GEOIPUPDATE_LICENSE_KEY: "{{ .Values.geoip.licenseKey }}"
 | 
			
		||||
  GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
 | 
			
		||||
  GEOIPUPDATE_FREQUENCY: "8"
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										39
									
								
								helm/templates/geoip-deployment.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								helm/templates/geoip-deployment.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
{{- if .Values.geoip.enabled -}}
 | 
			
		||||
apiVersion: apps/v1
 | 
			
		||||
kind: Deployment
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-geoip
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "authentik.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
    k8s.goauthentik.io/component: geoip
 | 
			
		||||
spec:
 | 
			
		||||
  replicas: 1
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
      app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
      k8s.goauthentik.io/component: geoip
 | 
			
		||||
  template:
 | 
			
		||||
    metadata:
 | 
			
		||||
      labels:
 | 
			
		||||
        app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
        app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
        k8s.goauthentik.io/component: geoip
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: geoip
 | 
			
		||||
          image: "{{ .Values.geoip.image }}"
 | 
			
		||||
          envFrom:
 | 
			
		||||
            - configMapRef:
 | 
			
		||||
                name: {{ include "authentik.fullname" . }}-geoip-config
 | 
			
		||||
          volumeMounts:
 | 
			
		||||
            - name: geoip
 | 
			
		||||
              mountPath: /usr/share/GeoIP
 | 
			
		||||
      volumes:
 | 
			
		||||
        - name: geoip
 | 
			
		||||
          persistentVolumeClaim:
 | 
			
		||||
            claimName: {{ include "authentik.fullname" . }}-geoip
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										17
									
								
								helm/templates/geoip-pvc.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								helm/templates/geoip-pvc.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
{{- if .Values.geoip.enabled -}}
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: PersistentVolumeClaim
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-geoip
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "authentik.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
spec:
 | 
			
		||||
  accessModes:
 | 
			
		||||
  - ReadWriteMany
 | 
			
		||||
  resources:
 | 
			
		||||
    requests:
 | 
			
		||||
      storage: 1Gi
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										121
									
								
								helm/templates/prom-rules.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								helm/templates/prom-rules.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
---
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: PrometheusRule
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-static-rules
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "authentik.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
spec:
 | 
			
		||||
  groups:
 | 
			
		||||
  - name: Aggregate request counters
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_http_requests_before_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_before_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_unknown_latency_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_unknown_latency_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_ajax_requests_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_ajax_requests_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_before_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_before_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_unknown_latency_including_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_unknown_latency_including_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_body_total_bytes:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_body_total_bytes[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_streaming_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_streaming_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_body_total_bytes:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_body_total_bytes[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_method[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_total_by_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_method[30s])) by (job,method)
 | 
			
		||||
      - record: job:django_http_requests_total_by_transport:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_transport[30s])) by (job,transport)
 | 
			
		||||
      - record: job:django_http_requests_total_by_view:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view)
 | 
			
		||||
      - record: job:django_http_requests_total_by_view_transport_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view,transport,method)
 | 
			
		||||
      - record: job:django_http_responses_total_by_templatename:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_templatename[30s])) by (job,templatename)
 | 
			
		||||
      - record: job:django_http_responses_total_by_status:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_status[30s])) by (job,status)
 | 
			
		||||
      - record: job:django_http_responses_total_by_status_name_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_status_name_method[30s])) by (job,status,name,method)
 | 
			
		||||
      - record: job:django_http_responses_total_by_charset:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_charset[30s])) by (job,charset)
 | 
			
		||||
      - record: job:django_http_exceptions_total_by_type:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_exceptions_total_by_type[30s])) by (job,type)
 | 
			
		||||
      - record: job:django_http_exceptions_total_by_view:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_exceptions_total_by_view[30s])) by (job,view)
 | 
			
		||||
  - name: Aggregate latency histograms
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "50"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "95"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99.9"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "50"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "95"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99.9"
 | 
			
		||||
  - name: Aggregate model operations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_model_inserts_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_inserts_total[1m])) by (job, model)
 | 
			
		||||
      - record: job:django_model_updates_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_updates_total[1m])) by (job, model)
 | 
			
		||||
      - record: job:django_model_deletes_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_deletes_total[1m])) by (job, model)
 | 
			
		||||
  - name: Aggregate database operations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_db_new_connections_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_new_connections_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_new_connection_errors_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_new_connection_errors_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_execute_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_execute_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_execute_many_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_execute_many_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_errors_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_errors_total[30s])) by (alias, vendor, type)
 | 
			
		||||
  - name: Aggregate migrations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_migrations_applied_total:max
 | 
			
		||||
        expr: max(django_migrations_applied_total) by (job, connection)
 | 
			
		||||
      - record: job:django_migrations_unapplied_total:max
 | 
			
		||||
        expr: max(django_migrations_unapplied_total) by (job, connection)
 | 
			
		||||
  - name: Alerts
 | 
			
		||||
    rules:
 | 
			
		||||
      - alert: UnappliedMigrations
 | 
			
		||||
        expr: job:django_migrations_unapplied_total:max > 0
 | 
			
		||||
        for: 1m
 | 
			
		||||
        labels:
 | 
			
		||||
          severity: testing
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										17
									
								
								helm/templates/static-sm.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								helm/templates/static-sm.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: ServiceMonitor
 | 
			
		||||
metadata:
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "authentik.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-static-monitoring
 | 
			
		||||
spec:
 | 
			
		||||
  endpoints:
 | 
			
		||||
  - port: http
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      k8s.goauthentik.io/component: static
 | 
			
		||||
{{- end }}
 | 
			
		||||
@ -88,9 +88,17 @@ spec:
 | 
			
		||||
                secretKeyRef:
 | 
			
		||||
                  name: "{{ .Release.Name }}-postgresql"
 | 
			
		||||
                  key: "postgresql-password"
 | 
			
		||||
            {{ if .Values.geoip.enabled -}}
 | 
			
		||||
            - name: AUTHENTIK_AUTHENTIK__GEOIP
 | 
			
		||||
              value: /geoip/GeoLite2-City.mmdb
 | 
			
		||||
            {{- end }}
 | 
			
		||||
          volumeMounts:
 | 
			
		||||
            - name: authentik-uploads
 | 
			
		||||
              mountPath: /media
 | 
			
		||||
            {{ if .Values.geoip.enabled -}}
 | 
			
		||||
            - name: geoip
 | 
			
		||||
              mountPath: /geoip
 | 
			
		||||
            {{- end }}
 | 
			
		||||
          ports:
 | 
			
		||||
            - name: http
 | 
			
		||||
              containerPort: 8000
 | 
			
		||||
@ -116,3 +124,8 @@ spec:
 | 
			
		||||
        - name: authentik-uploads
 | 
			
		||||
          persistentVolumeClaim:
 | 
			
		||||
            claimName: {{ include "authentik.fullname" . }}-uploads
 | 
			
		||||
        {{ if .Values.geoip.enabled -}}
 | 
			
		||||
        - name: geoip
 | 
			
		||||
          persistentVolumeClaim:
 | 
			
		||||
            claimName: {{ include "authentik.fullname" . }}-geoip
 | 
			
		||||
        {{- end }}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										26
									
								
								helm/templates/web-sm.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								helm/templates/web-sm.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: ServiceMonitor
 | 
			
		||||
metadata:
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "authentik.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "authentik.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
  name: {{ include "authentik.fullname" . }}-web-monitoring
 | 
			
		||||
spec:
 | 
			
		||||
  endpoints:
 | 
			
		||||
  - basicAuth:
 | 
			
		||||
      password:
 | 
			
		||||
        name: {{ include "authentik.fullname" . }}-secret-key
 | 
			
		||||
        key: SECRET_KEY
 | 
			
		||||
      username:
 | 
			
		||||
        name: {{ include "authentik.fullname" . }}-secret-key
 | 
			
		||||
        key: monitoring_username
 | 
			
		||||
    port: http
 | 
			
		||||
    path: /metrics/
 | 
			
		||||
    interval: 10s
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      k8s.goauthentik.io/component: web
 | 
			
		||||
{{- end }}
 | 
			
		||||
@ -68,6 +68,15 @@ spec:
 | 
			
		||||
                secretKeyRef:
 | 
			
		||||
                  name: "{{ .Release.Name }}-postgresql"
 | 
			
		||||
                  key: "postgresql-password"
 | 
			
		||||
            {{ if .Values.geoip.enabled -}}
 | 
			
		||||
            - name: AUTHENTIK_AUTHENTIK__GEOIP
 | 
			
		||||
              value: /geoip/GeoLite2-City.mmdb
 | 
			
		||||
            {{- end }}
 | 
			
		||||
          {{ if .Values.geoip.enabled -}}
 | 
			
		||||
          volumeMounts:
 | 
			
		||||
            - name: geoip
 | 
			
		||||
              mountPath: /geoip
 | 
			
		||||
          {{- end }}
 | 
			
		||||
          resources:
 | 
			
		||||
            requests:
 | 
			
		||||
              cpu: 150m
 | 
			
		||||
@ -75,3 +84,9 @@ spec:
 | 
			
		||||
            limits:
 | 
			
		||||
              cpu: 300m
 | 
			
		||||
              memory: 600M
 | 
			
		||||
      {{ if .Values.geoip.enabled -}}
 | 
			
		||||
      volumes:
 | 
			
		||||
        - name: geoip
 | 
			
		||||
          persistentVolumeClaim:
 | 
			
		||||
            claimName: {{ include "authentik.fullname" . }}-geoip
 | 
			
		||||
      {{- end -}}
 | 
			
		||||
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
image:
 | 
			
		||||
  tag: gh-master
 | 
			
		||||
  pullPolicy: Always
 | 
			
		||||
 | 
			
		||||
serverReplicas: 1
 | 
			
		||||
workerReplicas: 1
 | 
			
		||||
 | 
			
		||||
config:
 | 
			
		||||
  # Log level used by web and worker
 | 
			
		||||
  # Can be either debug, info, warning, error
 | 
			
		||||
  logLevel: debug
 | 
			
		||||
 | 
			
		||||
ingress:
 | 
			
		||||
  hosts:
 | 
			
		||||
    - authentik.127.0.0.1.nip.io
 | 
			
		||||
 | 
			
		||||
# These values influence the bundled postgresql and redis charts, but are also used by authentik to connect
 | 
			
		||||
postgresql:
 | 
			
		||||
  postgresqlPassword: EK-5jnKfjrGRm<77
 | 
			
		||||
 | 
			
		||||
redis:
 | 
			
		||||
  password: password
 | 
			
		||||
@ -5,7 +5,7 @@ image:
 | 
			
		||||
  name: beryju/authentik
 | 
			
		||||
  name_static: beryju/authentik-static
 | 
			
		||||
  name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
 | 
			
		||||
  tag: 2021.3.1-rc2
 | 
			
		||||
  tag: 2021.3.4
 | 
			
		||||
  pullPolicy: IfNotPresent
 | 
			
		||||
 | 
			
		||||
serverReplicas: 1
 | 
			
		||||
@ -14,6 +14,9 @@ workerReplicas: 1
 | 
			
		||||
# Enable the Kubernetes integration which lets authentik deploy outposts into kubernetes
 | 
			
		||||
kubernetesIntegration: true
 | 
			
		||||
 | 
			
		||||
monitoring:
 | 
			
		||||
  enabled: true
 | 
			
		||||
 | 
			
		||||
config:
 | 
			
		||||
  # Optionally specify fixed secret_key, otherwise generated automatically
 | 
			
		||||
  # secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
 | 
			
		||||
@ -41,6 +44,13 @@ config:
 | 
			
		||||
    # Email address authentik will send from, should have a correct @domain
 | 
			
		||||
    from: authentik@localhost
 | 
			
		||||
 | 
			
		||||
# Enable MaxMind GeoIP
 | 
			
		||||
geoip:
 | 
			
		||||
  enabled: false
 | 
			
		||||
  accountId: ""
 | 
			
		||||
  licenseKey: ""
 | 
			
		||||
  image: maxmindinc/geoipupdate:latest
 | 
			
		||||
 | 
			
		||||
# Enable Database Backups to S3
 | 
			
		||||
# backup:
 | 
			
		||||
#   accessKey: access-key
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ stages:
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'outpost/pkg/'
 | 
			
		||||
              artifact: 'swagger_client'
 | 
			
		||||
              artifact: 'go_swagger_client'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
  - stage: lint
 | 
			
		||||
    jobs:
 | 
			
		||||
@ -51,7 +51,7 @@ stages:
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'swagger_client'
 | 
			
		||||
              artifactName: 'go_swagger_client'
 | 
			
		||||
              path: "outpost/pkg/"
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
@ -70,7 +70,7 @@ stages:
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'swagger_client'
 | 
			
		||||
              artifactName: 'go_swagger_client'
 | 
			
		||||
              path: "outpost/pkg/"
 | 
			
		||||
          - task: Go@0
 | 
			
		||||
            inputs:
 | 
			
		||||
@ -89,7 +89,7 @@ stages:
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'swagger_client'
 | 
			
		||||
              artifactName: 'go_swagger_client'
 | 
			
		||||
              path: "outpost/pkg/"
 | 
			
		||||
          - task: Bash@3
 | 
			
		||||
            inputs:
 | 
			
		||||
@ -98,8 +98,8 @@ stages:
 | 
			
		||||
                python ./scripts/az_do_set_branch.py
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'GHCR'
 | 
			
		||||
              repository: 'beryju/authentik-proxy'
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/proxy'
 | 
			
		||||
              command: 'buildAndPush'
 | 
			
		||||
              Dockerfile: 'outpost/proxy.Dockerfile'
 | 
			
		||||
              buildContext: 'outpost/'
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ require (
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
 | 
			
		||||
	github.com/recws-org/recws v1.2.1
 | 
			
		||||
	github.com/sirupsen/logrus v1.8.0
 | 
			
		||||
	github.com/sirupsen/logrus v1.8.1
 | 
			
		||||
	github.com/spf13/afero v1.5.1 // indirect
 | 
			
		||||
	github.com/spf13/cast v1.3.1 // indirect
 | 
			
		||||
	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 | 
			
		||||
 | 
			
		||||
@ -618,6 +618,8 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM
 | 
			
		||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,3 @@
 | 
			
		||||
package pkg
 | 
			
		||||
 | 
			
		||||
const VERSION = "2021.3.1-rc2"
 | 
			
		||||
const VERSION = "2021.3.4"
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
FROM golang:1.16.0 AS builder
 | 
			
		||||
FROM golang:1.16.2 AS builder
 | 
			
		||||
 | 
			
		||||
WORKDIR /work
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3579
									
								
								swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										3579
									
								
								swagger.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -93,7 +93,10 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
 | 
			
		||||
        self.initial_stages()
 | 
			
		||||
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
 | 
			
		||||
        interface_admin = self.get_shadow_root("ak-interface-admin")
 | 
			
		||||
        wait = WebDriverWait(interface_admin, self.wait_timeout)
 | 
			
		||||
 | 
			
		||||
        wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
 | 
			
		||||
        self.driver.get(self.shell_url("authentik_core:user-settings"))
 | 
			
		||||
 | 
			
		||||
        user = User.objects.get(username="foo")
 | 
			
		||||
@ -188,7 +191,11 @@ class TestFlowsEnroll(SeleniumTestCase):
 | 
			
		||||
        self.driver.switch_to.window(self.driver.window_handles[0])
 | 
			
		||||
 | 
			
		||||
        # We're now logged in
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
 | 
			
		||||
        wait = WebDriverWait(
 | 
			
		||||
            self.get_shadow_root("ak-interface-admin"), self.wait_timeout
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
 | 
			
		||||
        self.driver.get(self.shell_url("authentik_core:user-settings"))
 | 
			
		||||
 | 
			
		||||
        self.assert_user(User.objects.get(username="foo"))
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										273
									
								
								tests/e2e/test_provider_oauth2_oidc_implicit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								tests/e2e/test_provider_oauth2_oidc_implicit.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,273 @@
 | 
			
		||||
"""test OAuth2 OpenID Provider flow"""
 | 
			
		||||
from json import loads
 | 
			
		||||
from sys import platform
 | 
			
		||||
from time import sleep
 | 
			
		||||
from unittest.case import skipUnless
 | 
			
		||||
 | 
			
		||||
from docker import DockerClient, from_env
 | 
			
		||||
from docker.models.containers import Container
 | 
			
		||||
from docker.types import Healthcheck
 | 
			
		||||
from selenium.webdriver.common.by import By
 | 
			
		||||
from selenium.webdriver.support import expected_conditions as ec
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import Application
 | 
			
		||||
from authentik.crypto.models import CertificateKeyPair
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.policies.expression.models import ExpressionPolicy
 | 
			
		||||
from authentik.policies.models import PolicyBinding
 | 
			
		||||
from authentik.providers.oauth2.constants import (
 | 
			
		||||
    SCOPE_OPENID,
 | 
			
		||||
    SCOPE_OPENID_EMAIL,
 | 
			
		||||
    SCOPE_OPENID_PROFILE,
 | 
			
		||||
)
 | 
			
		||||
from authentik.providers.oauth2.generators import (
 | 
			
		||||
    generate_client_id,
 | 
			
		||||
    generate_client_secret,
 | 
			
		||||
)
 | 
			
		||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
 | 
			
		||||
from tests.e2e.utils import (
 | 
			
		||||
    USER,
 | 
			
		||||
    SeleniumTestCase,
 | 
			
		||||
    apply_migration,
 | 
			
		||||
    object_manager,
 | 
			
		||||
    retry,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@skipUnless(platform.startswith("linux"), "requires local docker")
 | 
			
		||||
class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
 | 
			
		||||
    """test OAuth with OpenID Provider flow"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.client_id = generate_client_id()
 | 
			
		||||
        self.client_secret = generate_client_secret()
 | 
			
		||||
        self.application_slug = "test"
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
    def setup_client(self) -> Container:
 | 
			
		||||
        """Setup client saml-sp container which we test SAML against"""
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        client: DockerClient = from_env()
 | 
			
		||||
        container = client.containers.run(
 | 
			
		||||
            image="beryju/oidc-test-client",
 | 
			
		||||
            detach=True,
 | 
			
		||||
            network_mode="host",
 | 
			
		||||
            auto_remove=True,
 | 
			
		||||
            healthcheck=Healthcheck(
 | 
			
		||||
                test=["CMD", "wget", "--spider", "http://localhost:9009/health"],
 | 
			
		||||
                interval=5 * 100 * 1000000,
 | 
			
		||||
                start_period=1 * 100 * 1000000,
 | 
			
		||||
            ),
 | 
			
		||||
            environment={
 | 
			
		||||
                "OIDC_CLIENT_ID": self.client_id,
 | 
			
		||||
                "OIDC_CLIENT_SECRET": self.client_secret,
 | 
			
		||||
                "OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        while True:
 | 
			
		||||
            container.reload()
 | 
			
		||||
            status = container.attrs.get("State", {}).get("Health", {}).get("Status")
 | 
			
		||||
            if status == "healthy":
 | 
			
		||||
                return container
 | 
			
		||||
            LOGGER.info("Container failed healthcheck")
 | 
			
		||||
            sleep(1)
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_migration("authentik_core", "0003_default_user")
 | 
			
		||||
    @apply_migration("authentik_flows", "0008_default_flows")
 | 
			
		||||
    @apply_migration("authentik_flows", "0010_provider_flows")
 | 
			
		||||
    @apply_migration("authentik_crypto", "0002_create_self_signed_kp")
 | 
			
		||||
    def test_redirect_uri_error(self):
 | 
			
		||||
        """test OpenID Provider flow (invalid redirect URI, check error message)"""
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        # Bootstrap all needed objects
 | 
			
		||||
        authorization_flow = Flow.objects.get(
 | 
			
		||||
            slug="default-provider-authorization-implicit-consent"
 | 
			
		||||
        )
 | 
			
		||||
        provider = OAuth2Provider.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            client_type=ClientTypes.CONFIDENTIAL,
 | 
			
		||||
            client_id=self.client_id,
 | 
			
		||||
            client_secret=self.client_secret,
 | 
			
		||||
            rsa_key=CertificateKeyPair.objects.first(),
 | 
			
		||||
            redirect_uris="http://localhost:9009/",
 | 
			
		||||
            authorization_flow=authorization_flow,
 | 
			
		||||
        )
 | 
			
		||||
        provider.property_mappings.set(
 | 
			
		||||
            ScopeMapping.objects.filter(
 | 
			
		||||
                scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        provider.save()
 | 
			
		||||
        Application.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            slug=self.application_slug,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
        )
 | 
			
		||||
        self.container = self.setup_client()
 | 
			
		||||
 | 
			
		||||
        self.driver.get("http://localhost:9009/implicit/")
 | 
			
		||||
        sleep(2)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.driver.find_element(By.CLASS_NAME, "pf-c-title").text,
 | 
			
		||||
            "Redirect URI Error",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_migration("authentik_core", "0003_default_user")
 | 
			
		||||
    @apply_migration("authentik_flows", "0008_default_flows")
 | 
			
		||||
    @apply_migration("authentik_flows", "0010_provider_flows")
 | 
			
		||||
    @apply_migration("authentik_crypto", "0002_create_self_signed_kp")
 | 
			
		||||
    @object_manager
 | 
			
		||||
    def test_authorization_consent_implied(self):
 | 
			
		||||
        """test OpenID Provider flow (default authorization flow with implied consent)"""
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        # Bootstrap all needed objects
 | 
			
		||||
        authorization_flow = Flow.objects.get(
 | 
			
		||||
            slug="default-provider-authorization-implicit-consent"
 | 
			
		||||
        )
 | 
			
		||||
        provider = OAuth2Provider.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            client_type=ClientTypes.CONFIDENTIAL,
 | 
			
		||||
            client_id=self.client_id,
 | 
			
		||||
            client_secret=self.client_secret,
 | 
			
		||||
            rsa_key=CertificateKeyPair.objects.first(),
 | 
			
		||||
            redirect_uris="http://localhost:9009/implicit/",
 | 
			
		||||
            authorization_flow=authorization_flow,
 | 
			
		||||
        )
 | 
			
		||||
        provider.property_mappings.set(
 | 
			
		||||
            ScopeMapping.objects.filter(
 | 
			
		||||
                scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        provider.save()
 | 
			
		||||
        Application.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            slug=self.application_slug,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
        )
 | 
			
		||||
        self.container = self.setup_client()
 | 
			
		||||
 | 
			
		||||
        self.driver.get("http://localhost:9009/implicit/")
 | 
			
		||||
        self.login()
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre")))
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
 | 
			
		||||
        print(body)
 | 
			
		||||
        self.assertEqual(body["profile"]["nickname"], USER().username)
 | 
			
		||||
        self.assertEqual(body["profile"]["name"], USER().name)
 | 
			
		||||
        self.assertEqual(body["profile"]["email"], USER().email)
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_migration("authentik_core", "0003_default_user")
 | 
			
		||||
    @apply_migration("authentik_flows", "0008_default_flows")
 | 
			
		||||
    @apply_migration("authentik_flows", "0010_provider_flows")
 | 
			
		||||
    @apply_migration("authentik_crypto", "0002_create_self_signed_kp")
 | 
			
		||||
    @object_manager
 | 
			
		||||
    def test_authorization_consent_explicit(self):
 | 
			
		||||
        """test OpenID Provider flow (default authorization flow with explicit consent)"""
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        # Bootstrap all needed objects
 | 
			
		||||
        authorization_flow = Flow.objects.get(
 | 
			
		||||
            slug="default-provider-authorization-explicit-consent"
 | 
			
		||||
        )
 | 
			
		||||
        provider = OAuth2Provider.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            authorization_flow=authorization_flow,
 | 
			
		||||
            client_type=ClientTypes.CONFIDENTIAL,
 | 
			
		||||
            client_id=self.client_id,
 | 
			
		||||
            client_secret=self.client_secret,
 | 
			
		||||
            rsa_key=CertificateKeyPair.objects.first(),
 | 
			
		||||
            redirect_uris="http://localhost:9009/implicit/",
 | 
			
		||||
        )
 | 
			
		||||
        provider.property_mappings.set(
 | 
			
		||||
            ScopeMapping.objects.filter(
 | 
			
		||||
                scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        provider.save()
 | 
			
		||||
        app = Application.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            slug=self.application_slug,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
        )
 | 
			
		||||
        self.container = self.setup_client()
 | 
			
		||||
 | 
			
		||||
        self.driver.get("http://localhost:9009/implicit/")
 | 
			
		||||
        self.login()
 | 
			
		||||
 | 
			
		||||
        self.wait.until(
 | 
			
		||||
            ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor"))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        flow_executor = self.get_shadow_root("ak-flow-executor")
 | 
			
		||||
        consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
 | 
			
		||||
 | 
			
		||||
        self.assertIn(
 | 
			
		||||
            app.name,
 | 
			
		||||
            consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text,
 | 
			
		||||
        )
 | 
			
		||||
        consent_stage.find_element(
 | 
			
		||||
            By.CSS_SELECTOR,
 | 
			
		||||
            ("[type=submit]"),
 | 
			
		||||
        ).click()
 | 
			
		||||
 | 
			
		||||
        self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre")))
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(body["profile"]["nickname"], USER().username)
 | 
			
		||||
        self.assertEqual(body["profile"]["name"], USER().name)
 | 
			
		||||
        self.assertEqual(body["profile"]["email"], USER().email)
 | 
			
		||||
 | 
			
		||||
    @retry()
 | 
			
		||||
    @apply_migration("authentik_core", "0003_default_user")
 | 
			
		||||
    @apply_migration("authentik_flows", "0008_default_flows")
 | 
			
		||||
    @apply_migration("authentik_flows", "0010_provider_flows")
 | 
			
		||||
    @apply_migration("authentik_crypto", "0002_create_self_signed_kp")
 | 
			
		||||
    def test_authorization_denied(self):
 | 
			
		||||
        """test OpenID Provider flow (default authorization with access deny)"""
 | 
			
		||||
        sleep(1)
 | 
			
		||||
        # Bootstrap all needed objects
 | 
			
		||||
        authorization_flow = Flow.objects.get(
 | 
			
		||||
            slug="default-provider-authorization-explicit-consent"
 | 
			
		||||
        )
 | 
			
		||||
        provider = OAuth2Provider.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            authorization_flow=authorization_flow,
 | 
			
		||||
            client_type=ClientTypes.CONFIDENTIAL,
 | 
			
		||||
            client_id=self.client_id,
 | 
			
		||||
            client_secret=self.client_secret,
 | 
			
		||||
            rsa_key=CertificateKeyPair.objects.first(),
 | 
			
		||||
            redirect_uris="http://localhost:9009/implicit/",
 | 
			
		||||
        )
 | 
			
		||||
        provider.property_mappings.set(
 | 
			
		||||
            ScopeMapping.objects.filter(
 | 
			
		||||
                scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        provider.save()
 | 
			
		||||
        app = Application.objects.create(
 | 
			
		||||
            name=self.application_slug,
 | 
			
		||||
            slug=self.application_slug,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        negative_policy = ExpressionPolicy.objects.create(
 | 
			
		||||
            name="negative-static", expression="return False"
 | 
			
		||||
        )
 | 
			
		||||
        PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
 | 
			
		||||
 | 
			
		||||
        self.container = self.setup_client()
 | 
			
		||||
        self.driver.get("http://localhost:9009/implicit/")
 | 
			
		||||
        self.login()
 | 
			
		||||
        self.wait.until(
 | 
			
		||||
            ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.driver.find_element(By.CSS_SELECTOR, "header > h1").text,
 | 
			
		||||
            "Permission denied",
 | 
			
		||||
        )
 | 
			
		||||
@ -3,11 +3,13 @@ from shutil import rmtree
 | 
			
		||||
from tempfile import mkdtemp
 | 
			
		||||
from time import sleep
 | 
			
		||||
 | 
			
		||||
import yaml
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from docker import DockerClient, from_env
 | 
			
		||||
from docker.models.containers import Container
 | 
			
		||||
from docker.types.healthcheck import Healthcheck
 | 
			
		||||
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.crypto.models import CertificateKeyPair
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
@ -93,3 +95,14 @@ class OutpostDockerTests(TestCase):
 | 
			
		||||
        controller = DockerController(self.outpost, self.service_connection)
 | 
			
		||||
        controller.up()
 | 
			
		||||
        controller.down()
 | 
			
		||||
 | 
			
		||||
    def test_docker_static(self):
 | 
			
		||||
        """test that deployment requires update"""
 | 
			
		||||
        controller = DockerController(self.outpost, self.service_connection)
 | 
			
		||||
        manifest = controller.get_static_deployment()
 | 
			
		||||
        compose = yaml.load(manifest, Loader=yaml.SafeLoader)
 | 
			
		||||
        self.assertEqual(compose["version"], "3.5")
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            compose["services"]["authentik_proxy"]["image"],
 | 
			
		||||
            f"beryju/authentik-proxy:{__version__}",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										108
									
								
								tests/integration/test_proxy_docker.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								tests/integration/test_proxy_docker.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,108 @@
 | 
			
		||||
"""outpost tests"""
 | 
			
		||||
from shutil import rmtree
 | 
			
		||||
from tempfile import mkdtemp
 | 
			
		||||
from time import sleep
 | 
			
		||||
 | 
			
		||||
import yaml
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from docker import DockerClient, from_env
 | 
			
		||||
from docker.models.containers import Container
 | 
			
		||||
from docker.types.healthcheck import Healthcheck
 | 
			
		||||
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.crypto.models import CertificateKeyPair
 | 
			
		||||
from authentik.flows.models import Flow
 | 
			
		||||
from authentik.outposts.apps import AuthentikOutpostConfig
 | 
			
		||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
 | 
			
		||||
from authentik.providers.proxy.controllers.docker import DockerController
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestProxyDocker(TestCase):
 | 
			
		||||
    """Test Docker Controllers"""
 | 
			
		||||
 | 
			
		||||
    def _start_container(self, ssl_folder: str) -> Container:
 | 
			
		||||
        client: DockerClient = from_env()
 | 
			
		||||
        container = client.containers.run(
 | 
			
		||||
            image="library/docker:dind",
 | 
			
		||||
            detach=True,
 | 
			
		||||
            network_mode="host",
 | 
			
		||||
            remove=True,
 | 
			
		||||
            privileged=True,
 | 
			
		||||
            healthcheck=Healthcheck(
 | 
			
		||||
                test=["CMD", "docker", "info"],
 | 
			
		||||
                interval=5 * 100 * 1000000,
 | 
			
		||||
                start_period=5 * 100 * 1000000,
 | 
			
		||||
            ),
 | 
			
		||||
            environment={"DOCKER_TLS_CERTDIR": "/ssl"},
 | 
			
		||||
            volumes={
 | 
			
		||||
                f"{ssl_folder}/": {
 | 
			
		||||
                    "bind": "/ssl",
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        while True:
 | 
			
		||||
            container.reload()
 | 
			
		||||
            status = container.attrs.get("State", {}).get("Health", {}).get("Status")
 | 
			
		||||
            if status == "healthy":
 | 
			
		||||
                return container
 | 
			
		||||
            sleep(1)
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.ssl_folder = mkdtemp()
 | 
			
		||||
        self.container = self._start_container(self.ssl_folder)
 | 
			
		||||
        # Ensure that local connection have been created
 | 
			
		||||
        AuthentikOutpostConfig.init_local_connection()
 | 
			
		||||
        self.provider: ProxyProvider = ProxyProvider.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            internal_host="http://localhost",
 | 
			
		||||
            external_host="http://localhost",
 | 
			
		||||
            authorization_flow=Flow.objects.first(),
 | 
			
		||||
        )
 | 
			
		||||
        authentication_kp = CertificateKeyPair.objects.create(
 | 
			
		||||
            name="docker-authentication",
 | 
			
		||||
            certificate_data=open(f"{self.ssl_folder}/client/cert.pem").read(),
 | 
			
		||||
            key_data=open(f"{self.ssl_folder}/client/key.pem").read(),
 | 
			
		||||
        )
 | 
			
		||||
        verification_kp = CertificateKeyPair.objects.create(
 | 
			
		||||
            name="docker-verification",
 | 
			
		||||
            certificate_data=open(f"{self.ssl_folder}/client/ca.pem").read(),
 | 
			
		||||
        )
 | 
			
		||||
        self.service_connection = DockerServiceConnection.objects.create(
 | 
			
		||||
            url="https://localhost:2376",
 | 
			
		||||
            tls_verification=verification_kp,
 | 
			
		||||
            tls_authentication=authentication_kp,
 | 
			
		||||
        )
 | 
			
		||||
        self.outpost: Outpost = Outpost.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            type=OutpostType.PROXY,
 | 
			
		||||
            service_connection=self.service_connection,
 | 
			
		||||
        )
 | 
			
		||||
        self.outpost.providers.add(self.provider)
 | 
			
		||||
        self.outpost.save()
 | 
			
		||||
 | 
			
		||||
    def tearDown(self) -> None:
 | 
			
		||||
        super().tearDown()
 | 
			
		||||
        self.container.kill()
 | 
			
		||||
        try:
 | 
			
		||||
            rmtree(self.ssl_folder)
 | 
			
		||||
        except PermissionError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def test_docker_controller(self):
 | 
			
		||||
        """test that deployment requires update"""
 | 
			
		||||
        controller = DockerController(self.outpost, self.service_connection)
 | 
			
		||||
        controller.up()
 | 
			
		||||
        controller.down()
 | 
			
		||||
 | 
			
		||||
    def test_docker_static(self):
 | 
			
		||||
        """test that deployment requires update"""
 | 
			
		||||
        controller = DockerController(self.outpost, self.service_connection)
 | 
			
		||||
        manifest = controller.get_static_deployment()
 | 
			
		||||
        compose = yaml.load(manifest, Loader=yaml.SafeLoader)
 | 
			
		||||
        self.assertEqual(compose["version"], "3.5")
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            compose["services"]["authentik_proxy"]["image"],
 | 
			
		||||
            f"beryju/authentik-proxy:{__version__}",
 | 
			
		||||
        )
 | 
			
		||||
@ -9,7 +9,7 @@ from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesCont
 | 
			
		||||
from authentik.providers.proxy.models import ProxyProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestControllers(TestCase):
 | 
			
		||||
class TestProxyKubernetes(TestCase):
 | 
			
		||||
    """Test Controllers"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
 | 
			
		||||
@ -4,3 +4,8 @@ node_modules
 | 
			
		||||
dist
 | 
			
		||||
# don't lint nyc coverage output
 | 
			
		||||
coverage
 | 
			
		||||
# don't lint generated code
 | 
			
		||||
src/api/apis
 | 
			
		||||
src/api/models
 | 
			
		||||
src/api/index.ts
 | 
			
		||||
src/api/runtime.ts
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,25 @@ variables:
 | 
			
		||||
    branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - stage: generate
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: swagger_generate
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '12.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'web/src/api/'
 | 
			
		||||
              artifact: 'ts_swagger_client'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
  - stage: lint
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: eslint
 | 
			
		||||
@ -20,6 +39,11 @@ stages:
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '12.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'ts_swagger_client'
 | 
			
		||||
              path: "web/src/api/"
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
@ -37,6 +61,11 @@ stages:
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '12.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'ts_swagger_client'
 | 
			
		||||
              path: "web/src/api/"
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
@ -56,6 +85,11 @@ stages:
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '12.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'ts_swagger_client'
 | 
			
		||||
              path: "web/src/api/"
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
@ -71,16 +105,21 @@ stages:
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
        - task: Bash@3
 | 
			
		||||
          inputs:
 | 
			
		||||
            targetType: 'inline'
 | 
			
		||||
            script: |
 | 
			
		||||
              python ./scripts/az_do_set_branch.py
 | 
			
		||||
        - task: Docker@2
 | 
			
		||||
          inputs:
 | 
			
		||||
            containerRegistry: 'GHCR'
 | 
			
		||||
            repository: 'beryju/authentik-static'
 | 
			
		||||
            command: 'buildAndPush'
 | 
			
		||||
            Dockerfile: 'web/Dockerfile'
 | 
			
		||||
            tags: "gh-$(branchName)"
 | 
			
		||||
            buildContext: 'web/'
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'ts_swagger_client'
 | 
			
		||||
              path: "web/src/api/"
 | 
			
		||||
          - task: Bash@3
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetType: 'inline'
 | 
			
		||||
              script: |
 | 
			
		||||
                python ./scripts/az_do_set_branch.py
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/static'
 | 
			
		||||
              command: 'buildAndPush'
 | 
			
		||||
              Dockerfile: 'web/Dockerfile'
 | 
			
		||||
              tags: "gh-$(branchName)"
 | 
			
		||||
              buildContext: 'web/'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										406
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										406
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -56,6 +56,15 @@
 | 
			
		||||
                "strip-json-comments": "^3.1.1"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "globals": {
 | 
			
		||||
                    "version": "12.4.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "type-fest": "^0.8.1"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "ignore": {
 | 
			
		||||
                    "version": "4.0.6",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 | 
			
		||||
@ -103,9 +112,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@patternfly/patternfly": {
 | 
			
		||||
            "version": "4.87.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.87.3.tgz",
 | 
			
		||||
            "integrity": "sha512-hDNMPa7B1zKD8LWFZO4SS5hC/N+yvuci2sAn8HJd+EIbAvbMAUkRsyZ0/XO3BG3RVtpSlgq7q8x1pAHC/FTFuA=="
 | 
			
		||||
            "version": "4.90.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz",
 | 
			
		||||
            "integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw=="
 | 
			
		||||
        },
 | 
			
		||||
        "@rollup/plugin-typescript": {
 | 
			
		||||
            "version": "8.2.0",
 | 
			
		||||
@ -143,27 +152,27 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/browser": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-OAikFZ9EimD3noxMp8tA6Cf6qJcQ2U8k5QSgTPwdx+09nZOGJzbRFteK7WWmrS93ZJdzN61lpSQbg5v+bmmfbQ==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-K5UGyEePtVPZIFMoiRafhd4Ov0M1kdozVsVKIPZrOpJyjQdPNX+fYDNL/h0nVmgOlE2S/uu4fl4mEfe/6aLShw==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/core": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/utils": "6.2.1",
 | 
			
		||||
                "@sentry/core": "6.2.2",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "@sentry/utils": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/types": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
 | 
			
		||||
                },
 | 
			
		||||
                "@sentry/utils": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@sentry/types": "6.2.1",
 | 
			
		||||
                        "@sentry/types": "6.2.2",
 | 
			
		||||
                        "tslib": "^1.9.3"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
@ -175,48 +184,48 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/core": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-jPqQEtafxxDtLONhCbTHh/Uq8mZRhsfbwJTSVYfPVEe/ELfFZLQK7tP6rOh7zEWKbTkE0mE6XcaoH3ZRAhgrqg==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-qqWbvvXtymfXh7N5eEvk97MCnMURuyFIgqWdVD4MQM6yIfDCy36CyGfuQ3ViHTLZGdIfEOhLL9/f4kzf1RzqBA==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.2.1",
 | 
			
		||||
                "@sentry/minimal": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/utils": "6.2.1",
 | 
			
		||||
                "@sentry/hub": "6.2.2",
 | 
			
		||||
                "@sentry/minimal": "6.2.2",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "@sentry/utils": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/hub": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==",
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@sentry/types": "6.2.1",
 | 
			
		||||
                        "@sentry/utils": "6.2.1",
 | 
			
		||||
                        "@sentry/types": "6.2.2",
 | 
			
		||||
                        "@sentry/utils": "6.2.2",
 | 
			
		||||
                        "tslib": "^1.9.3"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "@sentry/minimal": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==",
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@sentry/hub": "6.2.1",
 | 
			
		||||
                        "@sentry/types": "6.2.1",
 | 
			
		||||
                        "@sentry/hub": "6.2.2",
 | 
			
		||||
                        "@sentry/types": "6.2.2",
 | 
			
		||||
                        "tslib": "^1.9.3"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "@sentry/types": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
 | 
			
		||||
                },
 | 
			
		||||
                "@sentry/utils": {
 | 
			
		||||
                    "version": "6.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
 | 
			
		||||
                    "version": "6.2.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@sentry/types": "6.2.1",
 | 
			
		||||
                        "@sentry/types": "6.2.2",
 | 
			
		||||
                        "tslib": "^1.9.3"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
@ -228,12 +237,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/hub": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/utils": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "@sentry/utils": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -245,12 +254,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/minimal": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/hub": "6.2.2",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -262,14 +271,14 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/tracing": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-bvStY1SnL08wkSeVK3j9K5rivQQJdKFCPR2VYRFOCaUoleZ6ChPUnBvxQ/E2LXc0hk/y/wo1q4r5B0dfCCY+bQ==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-mAkPoqtofNfka/u9rOVVDQPaEoTmr0AQh654g9ZqsaqsOJLKjB4FDLVNubWs90fjeKqHiYkI3ZHPak2TzHBPkw==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.2.1",
 | 
			
		||||
                "@sentry/minimal": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/utils": "6.2.1",
 | 
			
		||||
                "@sentry/hub": "6.2.2",
 | 
			
		||||
                "@sentry/minimal": "6.2.2",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "@sentry/utils": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -281,16 +290,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/types": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/utils": {
 | 
			
		||||
            "version": "6.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
 | 
			
		||||
            "version": "6.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/types": "6.2.1",
 | 
			
		||||
                "@sentry/types": "6.2.2",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -310,12 +319,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@types/clean-css": {
 | 
			
		||||
            "version": "4.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-xiTJn3bmDh1lA8c6iVJs4ZhHw+pcmxXlJQXOB6G1oULaak8rmarIeFKI4aTJ7849dEhaO612wgIualZfbxTJwA==",
 | 
			
		||||
            "version": "4.2.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.3.tgz",
 | 
			
		||||
            "integrity": "sha512-ET0ldU/vpXecy5vO8JRIhtJWSrk1vzXdJcp3Bjf8bARZynl6vfkhEKY/A7njfNIRlmyTGuVFuqnD6I3tOGdXpQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@types/node": "*"
 | 
			
		||||
                "@types/node": "*",
 | 
			
		||||
                "source-map": "^0.6.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@types/codemirror": {
 | 
			
		||||
@ -404,80 +414,96 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@types/uglify-js": {
 | 
			
		||||
            "version": "3.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q==",
 | 
			
		||||
            "version": "3.13.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz",
 | 
			
		||||
            "integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "source-map": "^0.6.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==",
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-Lzkc/2+7EoH7+NjIWLS2lVuKKqbEmJhtXe3rmfA8cyiKnZm3IfLf51irnBcmow8Q/AptVV0XBZmBJKuUJTe6cQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.18.0",
 | 
			
		||||
                "debug": "^4.1.1",
 | 
			
		||||
                "functional-red-black-tree": "^1.0.1",
 | 
			
		||||
                "lodash": "^4.17.15",
 | 
			
		||||
                "regexpp": "^3.0.0",
 | 
			
		||||
                "semver": "^7.3.2",
 | 
			
		||||
                "tsutils": "^3.17.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/experimental-utils": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@types/json-schema": "^7.0.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.16.1",
 | 
			
		||||
                "eslint-scope": "^5.0.0",
 | 
			
		||||
                "eslint-utils": "^2.0.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/parser": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.16.1",
 | 
			
		||||
                "debug": "^4.1.1"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": {
 | 
			
		||||
                    "version": "4.16.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==",
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-olX4yN6rvHR2eyFOcb6E4vmhDPsfdMyfQ3qR+oQNkAv8emKKlfxTWUXU5Mqxs2Fwe3Pf1BoPvrwZtwngxDzYzQ==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                        "@typescript-eslint/visitor-keys": "4.16.1"
 | 
			
		||||
                        "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                        "@typescript-eslint/visitor-keys": "4.18.0"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "@typescript-eslint/types": {
 | 
			
		||||
                    "version": "4.16.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==",
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-/BRociARpj5E+9yQ7cwCF/SNOWwXJ3qhjurMuK2hIFUbr9vTuDeu476Zpu+ptxY2kSxUHDGLLKy+qGq2sOg37A==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                },
 | 
			
		||||
                "@typescript-eslint/visitor-keys": {
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-Q9t90JCvfYaN0OfFUgaLqByOfz8yPeTAdotn/XYNm5q9eHax90gzdb+RJ6E9T5s97Kv/UHWKERTmqA0jTKAEHw==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                        "eslint-visitor-keys": "^2.0.0"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/experimental-utils": {
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-92h723Kblt9JcT2RRY3QS2xefFKar4ZQFVs3GityOKWQYgtajxt/tuXIzL7sVCUlM1hgreiV5gkGYyBpdOwO6A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@types/json-schema": "^7.0.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.18.0",
 | 
			
		||||
                "eslint-scope": "^5.0.0",
 | 
			
		||||
                "eslint-utils": "^2.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": {
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-olX4yN6rvHR2eyFOcb6E4vmhDPsfdMyfQ3qR+oQNkAv8emKKlfxTWUXU5Mqxs2Fwe3Pf1BoPvrwZtwngxDzYzQ==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                        "@typescript-eslint/visitor-keys": "4.18.0"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "@typescript-eslint/types": {
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-/BRociARpj5E+9yQ7cwCF/SNOWwXJ3qhjurMuK2hIFUbr9vTuDeu476Zpu+ptxY2kSxUHDGLLKy+qGq2sOg37A==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                },
 | 
			
		||||
                "@typescript-eslint/typescript-estree": {
 | 
			
		||||
                    "version": "4.16.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==",
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-wt4xvF6vvJI7epz+rEqxmoNQ4ZADArGQO9gDU+cM0U5fdVv7N+IAuVoVAoZSOZxzGHBfvE3XQMLdy+scsqFfeg==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                        "@typescript-eslint/visitor-keys": "4.16.1",
 | 
			
		||||
                        "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                        "@typescript-eslint/visitor-keys": "4.18.0",
 | 
			
		||||
                        "debug": "^4.1.1",
 | 
			
		||||
                        "globby": "^11.0.1",
 | 
			
		||||
                        "is-glob": "^4.0.1",
 | 
			
		||||
@ -486,12 +512,12 @@
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "@typescript-eslint/visitor-keys": {
 | 
			
		||||
                    "version": "4.16.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==",
 | 
			
		||||
                    "version": "4.18.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.18.0.tgz",
 | 
			
		||||
                    "integrity": "sha512-Q9t90JCvfYaN0OfFUgaLqByOfz8yPeTAdotn/XYNm5q9eHax90gzdb+RJ6E9T5s97Kv/UHWKERTmqA0jTKAEHw==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                        "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                        "eslint-visitor-keys": "^2.0.0"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
@ -511,30 +537,42 @@
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==",
 | 
			
		||||
        "@typescript-eslint/parser": {
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-W3z5S0ZbecwX3PhJEAnq4mnjK5JJXvXUDBYIYGoweCyWyuvAKfGHvzmpUzgB5L4cRBb+cTu9U/ro66dx7dIimA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.16.1"
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.18.0",
 | 
			
		||||
                "debug": "^4.1.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-olX4yN6rvHR2eyFOcb6E4vmhDPsfdMyfQ3qR+oQNkAv8emKKlfxTWUXU5Mqxs2Fwe3Pf1BoPvrwZtwngxDzYzQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.18.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/types": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==",
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-/BRociARpj5E+9yQ7cwCF/SNOWwXJ3qhjurMuK2hIFUbr9vTuDeu476Zpu+ptxY2kSxUHDGLLKy+qGq2sOg37A==",
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/typescript-estree": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==",
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-wt4xvF6vvJI7epz+rEqxmoNQ4ZADArGQO9gDU+cM0U5fdVv7N+IAuVoVAoZSOZxzGHBfvE3XQMLdy+scsqFfeg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.18.0",
 | 
			
		||||
                "debug": "^4.1.1",
 | 
			
		||||
                "globby": "^11.0.1",
 | 
			
		||||
                "is-glob": "^4.0.1",
 | 
			
		||||
@ -559,12 +597,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/visitor-keys": {
 | 
			
		||||
            "version": "4.16.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz",
 | 
			
		||||
            "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==",
 | 
			
		||||
            "version": "4.18.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.18.0.tgz",
 | 
			
		||||
            "integrity": "sha512-Q9t90JCvfYaN0OfFUgaLqByOfz8yPeTAdotn/XYNm5q9eHax90gzdb+RJ6E9T5s97Kv/UHWKERTmqA0jTKAEHw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.16.1",
 | 
			
		||||
                "@typescript-eslint/types": "4.18.0",
 | 
			
		||||
                "eslint-visitor-keys": "^2.0.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
@ -1103,9 +1141,9 @@
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "eslint": {
 | 
			
		||||
            "version": "7.21.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz",
 | 
			
		||||
            "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==",
 | 
			
		||||
            "version": "7.22.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz",
 | 
			
		||||
            "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@babel/code-frame": "7.12.11",
 | 
			
		||||
@ -1125,7 +1163,7 @@
 | 
			
		||||
                "file-entry-cache": "^6.0.1",
 | 
			
		||||
                "functional-red-black-tree": "^1.0.1",
 | 
			
		||||
                "glob-parent": "^5.0.0",
 | 
			
		||||
                "globals": "^12.1.0",
 | 
			
		||||
                "globals": "^13.6.0",
 | 
			
		||||
                "ignore": "^4.0.6",
 | 
			
		||||
                "import-fresh": "^3.0.0",
 | 
			
		||||
                "imurmurhash": "^0.1.4",
 | 
			
		||||
@ -1133,7 +1171,7 @@
 | 
			
		||||
                "js-yaml": "^3.13.1",
 | 
			
		||||
                "json-stable-stringify-without-jsonify": "^1.0.1",
 | 
			
		||||
                "levn": "^0.4.1",
 | 
			
		||||
                "lodash": "^4.17.20",
 | 
			
		||||
                "lodash": "^4.17.21",
 | 
			
		||||
                "minimatch": "^3.0.4",
 | 
			
		||||
                "natural-compare": "^1.4.0",
 | 
			
		||||
                "optionator": "^0.9.1",
 | 
			
		||||
@ -1202,6 +1240,12 @@
 | 
			
		||||
                    "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                },
 | 
			
		||||
                "lodash": {
 | 
			
		||||
                    "version": "4.17.21",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 | 
			
		||||
                    "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                },
 | 
			
		||||
                "supports-color": {
 | 
			
		||||
                    "version": "7.2.0",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 | 
			
		||||
@ -1662,12 +1706,20 @@
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "globals": {
 | 
			
		||||
            "version": "12.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
 | 
			
		||||
            "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
 | 
			
		||||
            "version": "13.6.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/globals/-/globals-13.6.0.tgz",
 | 
			
		||||
            "integrity": "sha512-YFKCX0SiPg7l5oKYCJ2zZGxcXprVXHcSnVuvzrT3oSENQonVLqM5pf9fN5dLGZGyCjhw8TN8Btwe/jKnZ0pjvQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "type-fest": "^0.8.1"
 | 
			
		||||
                "type-fest": "^0.20.2"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "type-fest": {
 | 
			
		||||
                    "version": "0.20.2",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
 | 
			
		||||
                    "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "globby": {
 | 
			
		||||
@ -2280,16 +2332,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "minify-html-literals": {
 | 
			
		||||
            "version": "1.3.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.2.tgz",
 | 
			
		||||
            "integrity": "sha512-DBdi0md84vjvwmLoo9xleFV5FkhzOwfKBqcmoVFL54c9CFlSBtG9KTKEQqiwscB+acewculqys1cDnwyrYlNtg==",
 | 
			
		||||
            "version": "1.3.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz",
 | 
			
		||||
            "integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@types/html-minifier": "^3.5.3",
 | 
			
		||||
                "clean-css": "^4.2.1",
 | 
			
		||||
                "html-minifier": "^4.0.0",
 | 
			
		||||
                "magic-string": "^0.25.0",
 | 
			
		||||
                "parse-literals": "^1.2.0"
 | 
			
		||||
                "parse-literals": "^1.2.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "minimatch": {
 | 
			
		||||
@ -2489,20 +2541,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "parse-literals": {
 | 
			
		||||
            "version": "1.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-gh4zPwvFSXx9ginX8lu9MP3OPHN3VV12PXI8IXD6oMCklFqM82pfbU9e/PKf9r7oLpbqlDSDyHYSVlxxuq3Iew==",
 | 
			
		||||
            "version": "1.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "typescript": "^2.9.2 || ^3.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "typescript": {
 | 
			
		||||
                    "version": "3.9.7",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
 | 
			
		||||
                    "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
 | 
			
		||||
                    "dev": true
 | 
			
		||||
                }
 | 
			
		||||
                "typescript": "^2.9.2 || ^3.0.0 || ^4.0.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "parse5": {
 | 
			
		||||
@ -2717,9 +2761,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "rollup": {
 | 
			
		||||
            "version": "2.40.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.40.0.tgz",
 | 
			
		||||
            "integrity": "sha512-WiOGAPbXoHu+TOz6hyYUxIksOwsY/21TRWoO593jgYt8mvYafYqQl+axaA8y1z2HFazNUUrsMSjahV2A6/2R9A==",
 | 
			
		||||
            "version": "2.41.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.41.2.tgz",
 | 
			
		||||
            "integrity": "sha512-6u8fJJXJx6fmvKrAC9DHYZgONvSkz8S9b/VFBjoQ6dkKdHyPpPbpqiNl2Bao9XBzDHpq672X6sGZ9G1ZBqAHMg==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "fsevents": "~2.3.1"
 | 
			
		||||
            }
 | 
			
		||||
@ -2801,12 +2845,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "rollup-plugin-minify-html-literals": {
 | 
			
		||||
            "version": "1.2.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.5.tgz",
 | 
			
		||||
            "integrity": "sha512-x4FzCnbBpYdme7MQDS3+18CvYLqakAtM/JmA3hqXplwzMeZWW3l14KU7H33RhJlHH8Klgv49hGtBRLWLfjCudw==",
 | 
			
		||||
            "version": "1.2.6",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.6.tgz",
 | 
			
		||||
            "integrity": "sha512-JRq2fjlCTiw0zu+1Sy3ClHGCxA79dWGr4HLHWSQgd060StVW9fBVksuj8Xw/suPkNSGClJf/4xNQ1MF6JeXPaw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "minify-html-literals": "^1.3.2",
 | 
			
		||||
                "minify-html-literals": "^1.3.5",
 | 
			
		||||
                "rollup-pluginutils": "^2.8.2"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
@ -3246,9 +3290,9 @@
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "ajv": {
 | 
			
		||||
                    "version": "7.1.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==",
 | 
			
		||||
                    "version": "7.2.1",
 | 
			
		||||
                    "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.1.tgz",
 | 
			
		||||
                    "integrity": "sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ==",
 | 
			
		||||
                    "dev": true,
 | 
			
		||||
                    "requires": {
 | 
			
		||||
                        "fast-deep-equal": "^3.1.1",
 | 
			
		||||
@ -3383,15 +3427,15 @@
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "typescript": {
 | 
			
		||||
            "version": "4.2.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz",
 | 
			
		||||
            "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==",
 | 
			
		||||
            "version": "4.2.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
 | 
			
		||||
            "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "uglify-js": {
 | 
			
		||||
            "version": "3.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-e1KQFRCpOxnrJsJVqDUCjURq+wXvIn7cK2sRAx9XL3HYLL9aezOP4Pb1+Y3/o693EPk111Yj2Q+IUXxcpHlygQ==",
 | 
			
		||||
            "version": "3.13.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.0.tgz",
 | 
			
		||||
            "integrity": "sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==",
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "union-value": {
 | 
			
		||||
@ -3479,9 +3523,9 @@
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "v8-compile-cache": {
 | 
			
		||||
            "version": "2.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
 | 
			
		||||
            "version": "2.3.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
 | 
			
		||||
            "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
 | 
			
		||||
            "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "vscode-css-languageservice": {
 | 
			
		||||
 | 
			
		||||
@ -11,9 +11,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@fortawesome/fontawesome-free": "^5.15.2",
 | 
			
		||||
        "@patternfly/patternfly": "^4.87.3",
 | 
			
		||||
        "@sentry/browser": "^6.2.1",
 | 
			
		||||
        "@sentry/tracing": "^6.2.1",
 | 
			
		||||
        "@patternfly/patternfly": "^4.90.5",
 | 
			
		||||
        "@sentry/browser": "^6.2.2",
 | 
			
		||||
        "@sentry/tracing": "^6.2.2",
 | 
			
		||||
        "@types/chart.js": "^2.9.31",
 | 
			
		||||
        "@types/codemirror": "0.0.108",
 | 
			
		||||
        "@types/grecaptcha": "^3.0.1",
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
        "flowchart.js": "^1.15.0",
 | 
			
		||||
        "lit-element": "^2.4.0",
 | 
			
		||||
        "lit-html": "^1.3.0",
 | 
			
		||||
        "rollup": "^2.40.0",
 | 
			
		||||
        "rollup": "^2.41.2",
 | 
			
		||||
        "rollup-plugin-copy": "^3.4.0",
 | 
			
		||||
        "rollup-plugin-cssimport": "^1.0.2",
 | 
			
		||||
        "rollup-plugin-external-globals": "^0.6.1",
 | 
			
		||||
@ -33,17 +33,17 @@
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@rollup/plugin-typescript": "^8.2.0",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^4.16.1",
 | 
			
		||||
        "@typescript-eslint/parser": "^4.16.1",
 | 
			
		||||
        "eslint": "^7.21.0",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^4.18.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^4.18.0",
 | 
			
		||||
        "eslint": "^7.22.0",
 | 
			
		||||
        "eslint-config-google": "^0.14.0",
 | 
			
		||||
        "eslint-plugin-lit": "^1.3.0",
 | 
			
		||||
        "rollup-plugin-commonjs": "^10.1.0",
 | 
			
		||||
        "rollup-plugin-minify-html-literals": "^1.2.5",
 | 
			
		||||
        "rollup-plugin-minify-html-literals": "^1.2.6",
 | 
			
		||||
        "rollup-plugin-node-resolve": "^5.2.0",
 | 
			
		||||
        "rollup-plugin-sourcemaps": "^0.6.3",
 | 
			
		||||
        "rollup-plugin-terser": "^7.0.2",
 | 
			
		||||
        "ts-lit-plugin": "^1.2.1",
 | 
			
		||||
        "typescript": "^4.2.2"
 | 
			
		||||
        "typescript": "^4.2.3"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,16 +8,23 @@ import copy from "rollup-plugin-copy";
 | 
			
		||||
import externalGlobals from "rollup-plugin-external-globals";
 | 
			
		||||
 | 
			
		||||
const resources = [
 | 
			
		||||
    { src: "node_modules/@patternfly/patternfly/patternfly.css", dest: "dist/" },
 | 
			
		||||
    { src: "node_modules/@patternfly/patternfly/patternfly-addons.css", dest: "dist/" },
 | 
			
		||||
    { src: "node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css", dest: "dist/" },
 | 
			
		||||
    { src: "node_modules/@patternfly/patternfly/patternfly-base.css", dest: "dist/" },
 | 
			
		||||
    { src: "node_modules/@patternfly/patternfly/assets/*", dest: "dist/assets/" },
 | 
			
		||||
    { src: "src/index.html", dest: "dist" },
 | 
			
		||||
    { src: "src/authentik.css", dest: "dist" },
 | 
			
		||||
    { src: "src/assets/*", dest: "dist/assets" },
 | 
			
		||||
    { src: "./icons/*", dest: "dist/assets/icons" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 | 
			
		||||
function manualChunks(id) {
 | 
			
		||||
    if (id.includes("node_modules")) {
 | 
			
		||||
        return "vendor";
 | 
			
		||||
    }
 | 
			
		||||
    if (id.includes("src/api/")) {
 | 
			
		||||
        return "api";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
    {
 | 
			
		||||
        input: "./src/main.ts",
 | 
			
		||||
@ -26,6 +33,7 @@ export default [
 | 
			
		||||
                format: "es",
 | 
			
		||||
                dir: "dist",
 | 
			
		||||
                sourcemap: true,
 | 
			
		||||
                manualChunks: manualChunks,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
        plugins: [
 | 
			
		||||
@ -55,6 +63,7 @@ export default [
 | 
			
		||||
                format: "es",
 | 
			
		||||
                dir: "dist",
 | 
			
		||||
                sourcemap: true,
 | 
			
		||||
                manualChunks: manualChunks,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
        plugins: [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								web/src/api/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/src/api/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
apis/**
 | 
			
		||||
models/**
 | 
			
		||||
index.ts
 | 
			
		||||
runtime.ts
 | 
			
		||||
.openapi-generator/**
 | 
			
		||||
							
								
								
									
										23
									
								
								web/src/api/.openapi-generator-ignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/src/api/.openapi-generator-ignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
# OpenAPI Generator Ignore
 | 
			
		||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
 | 
			
		||||
 | 
			
		||||
# Use this file to prevent files from being overwritten by the generator.
 | 
			
		||||
# The patterns follow closely to .gitignore or .dockerignore.
 | 
			
		||||
 | 
			
		||||
# As an example, the C# client generator defines ApiClient.cs.
 | 
			
		||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
 | 
			
		||||
#ApiClient.cs
 | 
			
		||||
 | 
			
		||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
 | 
			
		||||
#foo/*/qux
 | 
			
		||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
 | 
			
		||||
 | 
			
		||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
 | 
			
		||||
#foo/**/qux
 | 
			
		||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
 | 
			
		||||
 | 
			
		||||
# You can also negate patterns with an exclamation (!).
 | 
			
		||||
# For example, you can ignore all files in a docs folder with the file extension .md:
 | 
			
		||||
#docs/*.md
 | 
			
		||||
# Then explicitly reverse the ignore rule for a single file:
 | 
			
		||||
#!docs/README.md
 | 
			
		||||
@ -1,32 +0,0 @@
 | 
			
		||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
 | 
			
		||||
import { Provider } from "./Providers";
 | 
			
		||||
 | 
			
		||||
export class Application {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    slug: string;
 | 
			
		||||
    provider?: Provider;
 | 
			
		||||
 | 
			
		||||
    launch_url: string;
 | 
			
		||||
    meta_launch_url: string;
 | 
			
		||||
    meta_icon: string;
 | 
			
		||||
    meta_description: string;
 | 
			
		||||
    meta_publisher: string;
 | 
			
		||||
    policies: string[];
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(slug: string): Promise<Application> {
 | 
			
		||||
        return DefaultClient.fetch<Application>(["core", "applications", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Application>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Application>>(["core", "applications"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/applications/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
 | 
			
		||||
 | 
			
		||||
export class CertificateKeyPair {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    fingerprint: string;
 | 
			
		||||
    cert_expiry: number;
 | 
			
		||||
    cert_subject: string;
 | 
			
		||||
    private_key_available: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(slug: string): Promise<CertificateKeyPair> {
 | 
			
		||||
        return DefaultClient.fetch<CertificateKeyPair>(["crypto", "certificatekeypairs", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<CertificateKeyPair>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<CertificateKeyPair>>(["crypto", "certificatekeypairs"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/crypto/certificates/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,3 @@
 | 
			
		||||
import { getCookie } from "../utils";
 | 
			
		||||
import { NotFoundError, RequestError } from "./Error";
 | 
			
		||||
 | 
			
		||||
export const VERSION = "v2beta";
 | 
			
		||||
 | 
			
		||||
export interface QueryArguments {
 | 
			
		||||
    page?: number;
 | 
			
		||||
    page_size?: number;
 | 
			
		||||
@ -11,97 +6,27 @@ export interface QueryArguments {
 | 
			
		||||
 | 
			
		||||
export interface BaseInheritanceModel {
 | 
			
		||||
 | 
			
		||||
    object_type: string;
 | 
			
		||||
    objectType: string;
 | 
			
		||||
 | 
			
		||||
    verbose_name: string;
 | 
			
		||||
    verbose_name_plural: string;
 | 
			
		||||
    verboseName: string;
 | 
			
		||||
    verboseNamePlural: string;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Client {
 | 
			
		||||
    makeUrl(url: string[], query?: QueryArguments): string {
 | 
			
		||||
        let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
 | 
			
		||||
        if (query) {
 | 
			
		||||
            const queryString = Object.keys(query)
 | 
			
		||||
                .filter((k) => query[k] !== null)
 | 
			
		||||
                // we default to a string in query[k] as we've filtered out the null above
 | 
			
		||||
                // this is just for type-hinting
 | 
			
		||||
                .map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k] || ""))
 | 
			
		||||
                .join("&");
 | 
			
		||||
            builtUrl += `?${queryString}`;
 | 
			
		||||
        }
 | 
			
		||||
        return builtUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fetch<T>(url: string[], query?: QueryArguments): Promise<T> {
 | 
			
		||||
        const finalUrl = this.makeUrl(url, query);
 | 
			
		||||
        return fetch(finalUrl)
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                if (r.status > 300) {
 | 
			
		||||
                    switch (r.status) {
 | 
			
		||||
                    case 404:
 | 
			
		||||
                        throw new NotFoundError(`URL ${finalUrl} not found`);
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new RequestError(r.statusText);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return r;
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => r.json())
 | 
			
		||||
            .then((r) => <T>r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private writeRequest<T>(url: string[], body: T, method: string, query?: QueryArguments): Promise<T> {
 | 
			
		||||
        const finalUrl = this.makeUrl(url, query);
 | 
			
		||||
        const csrftoken = getCookie("authentik_csrf");
 | 
			
		||||
        const request = new Request(finalUrl, {
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json",
 | 
			
		||||
                "Content-Type": "application/json",
 | 
			
		||||
                "X-CSRFToken": csrftoken,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
        return fetch(request, {
 | 
			
		||||
            method: method,
 | 
			
		||||
            mode: "same-origin",
 | 
			
		||||
            body: JSON.stringify(body),
 | 
			
		||||
        })
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                if (r.status > 300) {
 | 
			
		||||
                    switch (r.status) {
 | 
			
		||||
                    case 404:
 | 
			
		||||
                        throw new NotFoundError(`URL ${finalUrl} not found`);
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new RequestError(r.statusText);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return r;
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => r.json())
 | 
			
		||||
            .then((r) => <T>r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update<T>(url: string[], body: T, query?: QueryArguments): Promise<T> {
 | 
			
		||||
        return this.writeRequest(url, body, "PATCH", query);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DefaultClient = new Client();
 | 
			
		||||
 | 
			
		||||
export interface PBPagination {
 | 
			
		||||
export interface AKPagination {
 | 
			
		||||
    next?: number;
 | 
			
		||||
    previous?: number;
 | 
			
		||||
 | 
			
		||||
    count: number;
 | 
			
		||||
    current: number;
 | 
			
		||||
    total_pages: number;
 | 
			
		||||
    totalPages: number;
 | 
			
		||||
 | 
			
		||||
    start_index: number;
 | 
			
		||||
    end_index: number;
 | 
			
		||||
    startIndex: number;
 | 
			
		||||
    endIndex: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AKResponse<T> {
 | 
			
		||||
    pagination: PBPagination;
 | 
			
		||||
    pagination: AKPagination;
 | 
			
		||||
 | 
			
		||||
    results: Array<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,42 +1,39 @@
 | 
			
		||||
import { DefaultClient } from "./Client";
 | 
			
		||||
import * as Sentry from "@sentry/browser";
 | 
			
		||||
import { Integrations } from "@sentry/tracing";
 | 
			
		||||
import { VERSION } from "../constants";
 | 
			
		||||
import { SentryIgnoredError } from "../common/errors";
 | 
			
		||||
import { Configuration } from "./runtime";
 | 
			
		||||
import { RootApi } from "./apis";
 | 
			
		||||
import { Config } from ".";
 | 
			
		||||
import { getCookie } from "../utils";
 | 
			
		||||
 | 
			
		||||
export class Config {
 | 
			
		||||
    branding_logo: string;
 | 
			
		||||
    branding_title: string;
 | 
			
		||||
 | 
			
		||||
    error_reporting_enabled: boolean;
 | 
			
		||||
    error_reporting_environment: string;
 | 
			
		||||
    error_reporting_send_pii: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
export const DEFAULT_CONFIG = new Configuration({
 | 
			
		||||
    basePath: "/api/v2beta",
 | 
			
		||||
    headers: {
 | 
			
		||||
        "X-CSRFToken": getCookie("authentik_csrf"),
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
    static get(): Promise<Config> {
 | 
			
		||||
        return DefaultClient.fetch<Config>(["root", "config"]).then((config) => {
 | 
			
		||||
            if (config.error_reporting_enabled) {
 | 
			
		||||
                Sentry.init({
 | 
			
		||||
                    dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
 | 
			
		||||
                    release: `authentik@${VERSION}`,
 | 
			
		||||
                    integrations: [
 | 
			
		||||
                        new Integrations.BrowserTracing(),
 | 
			
		||||
                    ],
 | 
			
		||||
                    tracesSampleRate: 0.6,
 | 
			
		||||
                    environment: config.error_reporting_environment,
 | 
			
		||||
                    beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
 | 
			
		||||
                        if (hint.originalException instanceof SentryIgnoredError) {
 | 
			
		||||
                            return null;
 | 
			
		||||
                        }
 | 
			
		||||
                        return event;
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                console.debug("authentik/config: Sentry enabled.");
 | 
			
		||||
            }
 | 
			
		||||
            return config;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
export function configureSentry(): Promise<Config> {
 | 
			
		||||
    return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
 | 
			
		||||
        if (config.errorReportingEnabled) {
 | 
			
		||||
            Sentry.init({
 | 
			
		||||
                dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
 | 
			
		||||
                release: `authentik@${VERSION}`,
 | 
			
		||||
                integrations: [
 | 
			
		||||
                    new Integrations.BrowserTracing(),
 | 
			
		||||
                ],
 | 
			
		||||
                tracesSampleRate: 0.6,
 | 
			
		||||
                environment: config.errorReportingEnvironment,
 | 
			
		||||
                beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
 | 
			
		||||
                    if (hint.originalException instanceof SentryIgnoredError) {
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
                    return event;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            console.debug("authentik/config: Sentry enabled.");
 | 
			
		||||
        }
 | 
			
		||||
        return config;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
 | 
			
		||||
import { Event } from "./Events";
 | 
			
		||||
 | 
			
		||||
export class Notification {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    severity: string;
 | 
			
		||||
    body: string;
 | 
			
		||||
    created: string;
 | 
			
		||||
    event?: Event;
 | 
			
		||||
    seen: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Notification> {
 | 
			
		||||
        return DefaultClient.fetch<Notification>(["events", "notifications", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Notification>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Notification>>(["events", "notifications"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static markSeen(pk: string): Promise<{seen: boolean}> {
 | 
			
		||||
        return DefaultClient.update(["events", "notifications", pk], {
 | 
			
		||||
            "seen": true
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
 | 
			
		||||
import { Group } from "./Groups";
 | 
			
		||||
 | 
			
		||||
export class Rule {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    transports: string[];
 | 
			
		||||
    severity: string;
 | 
			
		||||
    group?: Group;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Rule> {
 | 
			
		||||
        return DefaultClient.fetch<Rule>(["events", "rules", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Rule>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Rule>>(["events", "rules"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/events/rules/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
 | 
			
		||||
 | 
			
		||||
export class Transport {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    mode: string;
 | 
			
		||||
    mode_verbose: string;
 | 
			
		||||
    webhook_url: string;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Transport> {
 | 
			
		||||
        return DefaultClient.fetch<Transport>(["events", "transports", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Transport>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Transport>>(["events", "transports"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/events/transports/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
 | 
			
		||||
import { Event } from "./models";
 | 
			
		||||
 | 
			
		||||
export interface EventUser {
 | 
			
		||||
    pk: number;
 | 
			
		||||
@ -11,37 +11,7 @@ export interface EventContext {
 | 
			
		||||
    [key: string]: EventContext | string | number | string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Event {
 | 
			
		||||
    pk: string;
 | 
			
		||||
export interface EventWithContext extends Event {
 | 
			
		||||
    user: EventUser;
 | 
			
		||||
    action: string;
 | 
			
		||||
    app: string;
 | 
			
		||||
    context: EventContext;
 | 
			
		||||
    client_ip: string;
 | 
			
		||||
    created: string;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Event> {
 | 
			
		||||
        return DefaultClient.fetch<Event>(["events", "events", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Event>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Event>>(["events", "events"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // events/events/top_per_user/?filter_action=authorize_application
 | 
			
		||||
    static topForUser(action: string): Promise<TopNEvent[]> {
 | 
			
		||||
        return DefaultClient.fetch<TopNEvent[]>(["events", "events", "top_per_user"], {
 | 
			
		||||
            "filter_action": action,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TopNEvent {
 | 
			
		||||
    application: { [key: string]: string};
 | 
			
		||||
    counted_events: number;
 | 
			
		||||
    unique_users: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,4 @@
 | 
			
		||||
import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client";
 | 
			
		||||
import { TypeCreate } from "./Providers";
 | 
			
		||||
 | 
			
		||||
export enum ChallengeTypes {
 | 
			
		||||
    native = "native",
 | 
			
		||||
    response = "response",
 | 
			
		||||
    shell = "shell",
 | 
			
		||||
    redirect = "redirect",
 | 
			
		||||
}
 | 
			
		||||
import { ChallengeTypeEnum } from "./models";
 | 
			
		||||
 | 
			
		||||
export interface Error {
 | 
			
		||||
    code: string;
 | 
			
		||||
@ -18,11 +10,12 @@ export interface ErrorDict {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Challenge {
 | 
			
		||||
    type: ChallengeTypes;
 | 
			
		||||
    type: ChallengeTypeEnum;
 | 
			
		||||
    component?: string;
 | 
			
		||||
    title?: string;
 | 
			
		||||
    response_errors?: ErrorDict;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface WithUserInfoChallenge extends Challenge {
 | 
			
		||||
    pending_user: string;
 | 
			
		||||
    pending_user_avatar: string;
 | 
			
		||||
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
 | 
			
		||||
export interface ShellChallenge extends Challenge {
 | 
			
		||||
    body: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RedirectChallenge extends Challenge {
 | 
			
		||||
    to: string;
 | 
			
		||||
}
 | 
			
		||||
@ -44,104 +38,3 @@ export enum FlowDesignation {
 | 
			
		||||
    Recovery = "recovery",
 | 
			
		||||
    StageConfiguration = "stage_configuration",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Flow {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    policybindingmodel_ptr_id: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    slug: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
    designation: FlowDesignation;
 | 
			
		||||
    background: string;
 | 
			
		||||
    stages: string[];
 | 
			
		||||
    policies: string[];
 | 
			
		||||
    cache_count: number;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(slug: string): Promise<Flow> {
 | 
			
		||||
        return DefaultClient.fetch<Flow>(["flows", "instances", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static diagram(slug: string): Promise<{ diagram: string }> {
 | 
			
		||||
        return DefaultClient.fetch<{ diagram: string }>(["flows", "instances", slug, "diagram"]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Flow>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Flow>>(["flows", "instances"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static cached(): Promise<number> {
 | 
			
		||||
        return DefaultClient.fetch<{ count: number }>(["flows", "instances", "cached"]).then(r => {
 | 
			
		||||
            return r.count;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static executor(slug: string): Promise<Challenge> {
 | 
			
		||||
        return DefaultClient.fetch(["flows", "executor", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/flows/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Stage implements BaseInheritanceModel {
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    object_type: string;
 | 
			
		||||
    verbose_name: string;
 | 
			
		||||
    verbose_name_plural: string;
 | 
			
		||||
    flow_set: Flow[];
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(slug: string): Promise<Stage> {
 | 
			
		||||
        return DefaultClient.fetch<Stage>(["stages", "all", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Stage>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Stage>>(["stages", "all"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getTypes(): Promise<TypeCreate[]> {
 | 
			
		||||
        return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/stages/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FlowStageBinding {
 | 
			
		||||
 | 
			
		||||
    pk: string;
 | 
			
		||||
    policybindingmodel_ptr_id: string;
 | 
			
		||||
    target: string;
 | 
			
		||||
    stage: string;
 | 
			
		||||
    stage_obj: Stage;
 | 
			
		||||
    evaluate_on_plan: boolean;
 | 
			
		||||
    re_evaluate_policies: boolean;
 | 
			
		||||
    order: number;
 | 
			
		||||
    policies: string[];
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(slug: string): Promise<FlowStageBinding> {
 | 
			
		||||
        return DefaultClient.fetch<FlowStageBinding>(["flows", "bindings", slug]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<FlowStageBinding>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<FlowStageBinding>>(["flows", "bindings"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/stages/bindings/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,28 +0,0 @@
 | 
			
		||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
 | 
			
		||||
import { EventContext } from "./Events";
 | 
			
		||||
 | 
			
		||||
export class Group {
 | 
			
		||||
 | 
			
		||||
    pk: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    is_superuser: boolean;
 | 
			
		||||
    attributes: EventContext;
 | 
			
		||||
    parent?: Group;
 | 
			
		||||
    users: number[];
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Group> {
 | 
			
		||||
        return DefaultClient.fetch<Group>(["core", "groups", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/groups/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
 | 
			
		||||
import { EventContext } from "./Events";
 | 
			
		||||
import { User } from "./Users";
 | 
			
		||||
 | 
			
		||||
export class Invitation {
 | 
			
		||||
 | 
			
		||||
    pk: string;
 | 
			
		||||
    expires: number;
 | 
			
		||||
    fixed_date: EventContext;
 | 
			
		||||
    created_by: User;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        throw Error();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static get(pk: string): Promise<Invitation> {
 | 
			
		||||
        return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
 | 
			
		||||
        return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static adminUrl(rest: string): string {
 | 
			
		||||
        return `/administration/stages/invitations/${rest}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user