Merge pull request #941 from goauthentik/authenticated-sessions
Session management
This commit is contained in:
commit
fb6242d2d3
1
Pipfile
1
Pipfile
|
@ -45,6 +45,7 @@ uvicorn = {extras = ["standard"],version = "*"}
|
|||
webauthn = "*"
|
||||
xmlsec = "*"
|
||||
duo-client = "*"
|
||||
ua-parser = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "eb043e24ba05d5d78459a973fe0cd7c37dad1cca90431f68b6df773247c58cbb"
|
||||
"sha256": "4fa1ad681762c867a95410074f31ac5d00119e187e0f38982cd59fdf301cccf5"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -56,6 +56,7 @@
|
|||
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
|
||||
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.7.4.post0"
|
||||
},
|
||||
"aioredis": {
|
||||
|
@ -70,6 +71,7 @@
|
|||
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
|
||||
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.6"
|
||||
},
|
||||
"asgiref": {
|
||||
|
@ -77,6 +79,7 @@
|
|||
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
|
||||
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.4"
|
||||
},
|
||||
"async-timeout": {
|
||||
|
@ -84,6 +87,7 @@
|
|||
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||
],
|
||||
"markers": "python_full_version >= '3.5.3'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"attrs": {
|
||||
|
@ -91,6 +95,7 @@
|
|||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"autobahn": {
|
||||
|
@ -98,6 +103,7 @@
|
|||
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
|
||||
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==21.3.1"
|
||||
},
|
||||
"automat": {
|
||||
|
@ -116,24 +122,26 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:40ccb6ec2d7e5e4d250d630a245aae7aa1fcd43c3519e9808b444f083ca4014a",
|
||||
"sha256:e6fa13cd8f16a6c222104ab17e0439e24b6974f60e7af113a38a80f252457cb0"
|
||||
"sha256:1d24c6d1f5db4b52bb29f1dfe13fd3e9d95d9fa4634b0638a096f5a884173cde",
|
||||
"sha256:8ee8766813864796be6c87ad762c6da4bfef603977931854a38f49fe4db06495"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.83"
|
||||
"version": "==1.17.84"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:55d450b6bf0df642809fe88a6840d90dab6b6ad5ff3dccaa1faf9e085dfd864a",
|
||||
"sha256:b36c14cfe208969ee9f658b645cfc718c1700c593313787a3fd59b335f7a6e2c"
|
||||
"sha256:75e1397b80aa8757a26636b949eebd20b3cf67e8f1ed80dc01170907e06ea45d",
|
||||
"sha256:bc59eb748fcb07835613ebea6dcc2600ae1a8be0fae30e40b9c1e81b73262296"
|
||||
],
|
||||
"version": "==1.20.83"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.20.84"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
|
||||
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
|
||||
],
|
||||
"markers": "python_version ~= '3.5'",
|
||||
"version": "==4.2.2"
|
||||
},
|
||||
"cbor2": {
|
||||
|
@ -152,6 +160,7 @@
|
|||
"sha256:f0058d33b5eaffb176d6190d175a5391f13362f165881deea2b99e63b66ecf55",
|
||||
"sha256:f5df0ad8c16f7992bf24e5c9a53f03a11a990fd18253c3c335315bd25a34f832"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.3.0"
|
||||
},
|
||||
"celery": {
|
||||
|
@ -244,6 +253,7 @@
|
|||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"click": {
|
||||
|
@ -251,6 +261,7 @@
|
|||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"click-didyoumean": {
|
||||
|
@ -310,6 +321,7 @@
|
|||
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
|
||||
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"defusedxml": {
|
||||
|
@ -358,11 +370,11 @@
|
|||
},
|
||||
"django-otp": {
|
||||
"hashes": [
|
||||
"sha256:75a815747a0542cc5442e3a6396dfd272c49a0866bee2149ac57ecc36ddd3961",
|
||||
"sha256:cc657a0e7266cda6ab42f861bdc3840ed24f7e441bc7f249916174dd1a6375a0"
|
||||
"sha256:01b5888f0bde5125e139433aacb947e52d5c406fa56c9db43c3e8d75b5c323c4",
|
||||
"sha256:0d56dd2a7fbb6ee6e54557e036ca64add0bd3596f471794bad673b7637d5e935"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.5"
|
||||
"version": "==1.0.6"
|
||||
},
|
||||
"django-prometheus": {
|
||||
"hashes": [
|
||||
|
@ -440,6 +452,7 @@
|
|||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"geoip2": {
|
||||
|
@ -455,6 +468,7 @@
|
|||
"sha256:044d81b1e58012f8ebc71cc134e191c1fa312f543f1fbc99973afe28c25e3228",
|
||||
"sha256:b3ca7a8ff9ab3bdefee3ad5aefb11fc6485423767eee016f5942d8e606ca23fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.30.1"
|
||||
},
|
||||
"gunicorn": {
|
||||
|
@ -470,6 +484,7 @@
|
|||
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
|
||||
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"hiredis": {
|
||||
|
@ -516,6 +531,7 @@
|
|||
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
|
||||
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"httptools": {
|
||||
|
@ -564,6 +580,7 @@
|
|||
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
|
||||
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"jmespath": {
|
||||
|
@ -571,6 +588,7 @@
|
|||
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
|
||||
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"jsonschema": {
|
||||
|
@ -585,6 +603,7 @@
|
|||
"sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d",
|
||||
"sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.1.0"
|
||||
},
|
||||
"kubernetes": {
|
||||
|
@ -597,7 +616,10 @@
|
|||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
|
||||
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
|
||||
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
|
||||
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
|
||||
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
|
||||
],
|
||||
"index": "pypi",
|
||||
|
@ -659,6 +681,7 @@
|
|||
"hashes": [
|
||||
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.3"
|
||||
},
|
||||
"msgpack": {
|
||||
|
@ -734,6 +757,7 @@
|
|||
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
|
||||
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.1.0"
|
||||
},
|
||||
"oauthlib": {
|
||||
|
@ -741,6 +765,7 @@
|
|||
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
|
||||
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"packaging": {
|
||||
|
@ -756,6 +781,7 @@
|
|||
"sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa",
|
||||
"sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.1"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
|
@ -763,6 +789,7 @@
|
|||
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
|
||||
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.18"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
|
@ -808,15 +835,37 @@
|
|||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
|
||||
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
|
||||
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
|
||||
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
|
||||
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
|
||||
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
|
||||
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
||||
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
|
||||
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
|
||||
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
|
||||
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
|
||||
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"
|
||||
],
|
||||
"version": "==0.4.8"
|
||||
},
|
||||
"pyasn1-modules": {
|
||||
"hashes": [
|
||||
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
|
||||
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
|
||||
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405",
|
||||
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
|
||||
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
|
||||
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
|
||||
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
|
||||
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
|
||||
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
|
||||
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
|
||||
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
|
||||
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
|
||||
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
|
||||
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"
|
||||
],
|
||||
"version": "==0.2.8"
|
||||
},
|
||||
|
@ -825,6 +874,7 @@
|
|||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pycryptodome": {
|
||||
|
@ -868,6 +918,7 @@
|
|||
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
|
||||
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"pyjwt": {
|
||||
|
@ -890,12 +941,14 @@
|
|||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.17.3"
|
||||
},
|
||||
"python-dateutil": {
|
||||
|
@ -903,6 +956,7 @@
|
|||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-dotenv": {
|
||||
|
@ -959,6 +1013,7 @@
|
|||
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.5.3"
|
||||
},
|
||||
"requests": {
|
||||
|
@ -966,12 +1021,14 @@
|
|||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
|
||||
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc",
|
||||
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
|
||||
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.0"
|
||||
|
@ -1012,6 +1069,7 @@
|
|||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
|
@ -1019,6 +1077,7 @@
|
|||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"structlog": {
|
||||
|
@ -1074,6 +1133,7 @@
|
|||
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
|
||||
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.2.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
|
@ -1084,11 +1144,20 @@
|
|||
],
|
||||
"version": "==3.10.0.0"
|
||||
},
|
||||
"ua-parser": {
|
||||
"hashes": [
|
||||
"sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a",
|
||||
"sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"uritemplate": {
|
||||
"hashes": [
|
||||
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
|
||||
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"urllib3": {
|
||||
|
@ -1133,6 +1202,7 @@
|
|||
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
|
||||
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.0"
|
||||
},
|
||||
"watchgod": {
|
||||
|
@ -1162,6 +1232,7 @@
|
|||
"sha256:3e2bf58191d4619b161389a95bdce84ce9e0b24eb8107e7e590db682c2d0ca81",
|
||||
"sha256:abf306dc6351dcef07f4d40453037e51cc5d9da2ef60d0fc5d0fe3bcda255372"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"websockets": {
|
||||
|
@ -1193,20 +1264,20 @@
|
|||
},
|
||||
"xmlsec": {
|
||||
"hashes": [
|
||||
"sha256:17d2e66d4e3e601d210eed936b53c3eb44cddaef62f60b5c6ad5c18e948d926c",
|
||||
"sha256:2bc1b871b49d6580779805a4a1c2d835e834a2fa614fe40cf71931d11a8279cf",
|
||||
"sha256:52eded125c0d1ab72125105ef061370c6b06ab9bd37e29a61bc2f8a61205bae4",
|
||||
"sha256:72af9a5a747a5fe6e425d2be10daa43d18307dbe03498df3820fc3cd93daa148",
|
||||
"sha256:806855d505da24aeb77758a6f373b1473e5ed63bdbe346af90cc6d2b053e4716",
|
||||
"sha256:8746dd992aaec06ed8ff1615f4a8e2a32258e8af38f9a9f8acf3ee1fb34a5da6",
|
||||
"sha256:9d52b2b15d42292725e4f9d8a5b040e39cba0a9cd58059ac951e7310d6340bb9",
|
||||
"sha256:b380f3ebc042f71afab057632481d06e06f1ba4f90047d91ca92612a7d3d487b",
|
||||
"sha256:be0f475edd8e9c98f57449c97839f6a81946e79e4cccb81e4b5196a2cc40e044",
|
||||
"sha256:bf3c62d154f2222caf56d897ddfd53fd0aef560d5a2202447d90e015301a0a10",
|
||||
"sha256:fe6a5f05aba3ff47e105a308482b68f8b0fd80656eb1456a9c1e4de47d2c580f"
|
||||
"sha256:23f209260b37bdc2fd96af837494c47dd1e67964f077442b63acd83c0f62e212",
|
||||
"sha256:4fb38ab0bf3e47cbae136119674a869e09d61c939b510350f369c8ac46087373",
|
||||
"sha256:705ab5b848afdf3a5c78b1322276054c885f44dc51601e14cb883a9c86cbe20f",
|
||||
"sha256:843d10bba4c480609da74ee11fff1ee0fc1c12821c656979f12a7a4ecb043e03",
|
||||
"sha256:86d54b93f8278e2f0c504d0744e39a483c1c7ce9993f2ca70184cc7770faa982",
|
||||
"sha256:8922fba55a060ee81de4a7f5efc593c5bf121047763aecf0eead02e061c9d2db",
|
||||
"sha256:c7b49d4fce83186b89f7ce6cec765245d36a70d0acc2f3ed0ba95c735b3667da",
|
||||
"sha256:cd2eaaff7f31784a07dd99ce81fa767313df3ba1834faa4143ee2c07000cac7a",
|
||||
"sha256:dea5bef9b5830c36ccb7a68a0d94d49eaea4d03fbbd04179652bf661b7e6e30f",
|
||||
"sha256:eadff662d89c80db409c69d82eb3e695e16d4a5e8ab56b5b22670a54e9c6ff20",
|
||||
"sha256:ee233d0bc27fb8f447ca2622b0de2ac2df45b8795f02ef263825912011fe4fe9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.10"
|
||||
"version": "==1.3.11"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
|
@ -1248,6 +1319,7 @@
|
|||
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
|
||||
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"zope.interface": {
|
||||
|
@ -1304,6 +1376,7 @@
|
|||
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
|
||||
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==5.4.0"
|
||||
}
|
||||
},
|
||||
|
@ -1317,16 +1390,18 @@
|
|||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e",
|
||||
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"
|
||||
"sha256:3c9a2d84354185d13213ff2640ec03d39168dbcd13648abc84fb13ca3b2e2761",
|
||||
"sha256:d66a600e1602736a0f24f725a511b0e50d12eb18f54b31ec276d2c26a0a62c6a"
|
||||
],
|
||||
"version": "==2.5.6"
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.5.7"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"bandit": {
|
||||
|
@ -1365,6 +1440,7 @@
|
|||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"click": {
|
||||
|
@ -1372,6 +1448,7 @@
|
|||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"colorama": {
|
||||
|
@ -1445,6 +1522,7 @@
|
|||
"sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
|
||||
"sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==4.0.7"
|
||||
},
|
||||
"gitpython": {
|
||||
|
@ -1452,6 +1530,7 @@
|
|||
"sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135",
|
||||
"sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.1.17"
|
||||
},
|
||||
"idna": {
|
||||
|
@ -1473,6 +1552,7 @@
|
|||
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
|
||||
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
|
||||
],
|
||||
"markers": "python_version >= '3.6' and python_version < '4.0'",
|
||||
"version": "==5.8.0"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
|
@ -1500,6 +1580,7 @@
|
|||
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
|
||||
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"mccabe": {
|
||||
|
@ -1536,6 +1617,7 @@
|
|||
"sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd",
|
||||
"sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"
|
||||
],
|
||||
"markers": "python_version >= '2.6'",
|
||||
"version": "==5.6.0"
|
||||
},
|
||||
"pluggy": {
|
||||
|
@ -1543,6 +1625,7 @@
|
|||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
|
@ -1550,6 +1633,7 @@
|
|||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pylint": {
|
||||
|
@ -1580,6 +1664,7 @@
|
|||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytest": {
|
||||
|
@ -1684,6 +1769,7 @@
|
|||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.25.1"
|
||||
},
|
||||
"requests-mock": {
|
||||
|
@ -1707,6 +1793,7 @@
|
|||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"smmap": {
|
||||
|
@ -1714,6 +1801,7 @@
|
|||
"sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
|
||||
"sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"stevedore": {
|
||||
|
@ -1721,6 +1809,7 @@
|
|||
"sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee",
|
||||
"sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"toml": {
|
||||
|
@ -1728,6 +1817,7 @@
|
|||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"urllib3": {
|
||||
|
|
|
@ -11,6 +11,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.events.geo import GEOIP_READER
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
|
@ -18,6 +19,7 @@ class Capabilities(models.TextChoices):
|
|||
"""Define capabilities which influence which APIs can/should be used"""
|
||||
|
||||
CAN_SAVE_MEDIA = "can_save_media"
|
||||
CAN_GEO_IP = "can_geo_ip"
|
||||
|
||||
|
||||
class ConfigSerializer(PassiveSerializer):
|
||||
|
@ -41,6 +43,8 @@ class ConfigView(APIView):
|
|||
deb_test = settings.DEBUG or settings.TEST
|
||||
if path.ismount(settings.MEDIA_ROOT) or deb_test:
|
||||
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
||||
if GEOIP_READER:
|
||||
caps.append(Capabilities.CAN_GEO_IP)
|
||||
return caps
|
||||
|
||||
@extend_schema(responses={200: ConfigSerializer(many=False)})
|
||||
|
|
|
@ -12,6 +12,7 @@ from authentik.admin.api.workers import WorkerView
|
|||
from authentik.api.v2.config import ConfigView
|
||||
from authentik.api.views import APIBrowserView
|
||||
from authentik.core.api.applications import ApplicationViewSet
|
||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
||||
from authentik.core.api.groups import GroupViewSet
|
||||
from authentik.core.api.propertymappings import PropertyMappingViewSet
|
||||
from authentik.core.api.providers import ProviderViewSet
|
||||
|
@ -108,6 +109,7 @@ router = routers.DefaultRouter()
|
|||
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
|
||||
router.register("admin/apps", AppsViewSet, basename="apps")
|
||||
|
||||
router.register("core/authenticated_sessions", AuthenticatedSessionViewSet)
|
||||
router.register("core/applications", ApplicationViewSet)
|
||||
router.register("core/groups", GroupViewSet)
|
||||
router.register("core/users", UserViewSet)
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
"""AuthenticatedSessions API Viewset"""
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from geoip2.errors import GeoIP2Error
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from ua_parser import user_agent_parser
|
||||
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
from authentik.events.geo import GEOIP_READER
|
||||
|
||||
|
||||
class UserAgentDeviceDict(TypedDict):
|
||||
"""User agent device"""
|
||||
|
||||
brand: str
|
||||
family: str
|
||||
model: str
|
||||
|
||||
|
||||
class UserAgentOSDict(TypedDict):
|
||||
"""User agent os"""
|
||||
|
||||
family: str
|
||||
major: str
|
||||
minor: str
|
||||
patch: str
|
||||
patch_minor: str
|
||||
|
||||
|
||||
class UserAgentBrowserDict(TypedDict):
|
||||
"""User agent browser"""
|
||||
|
||||
family: str
|
||||
major: str
|
||||
minor: str
|
||||
patch: str
|
||||
|
||||
|
||||
class UserAgentDict(TypedDict):
|
||||
"""User agent details"""
|
||||
|
||||
device: UserAgentDeviceDict
|
||||
os: UserAgentOSDict
|
||||
user_agent: UserAgentBrowserDict
|
||||
string: str
|
||||
|
||||
|
||||
class GeoIPDict(TypedDict):
|
||||
"""GeoIP Details"""
|
||||
|
||||
continent: str
|
||||
country: str
|
||||
lat: float
|
||||
long: float
|
||||
|
||||
|
||||
class AuthenticatedSessionSerializer(ModelSerializer):
|
||||
"""AuthenticatedSession Serializer"""
|
||||
|
||||
current = SerializerMethodField()
|
||||
user_agent = SerializerMethodField()
|
||||
geo_ip = SerializerMethodField()
|
||||
|
||||
def get_current(self, instance: AuthenticatedSession) -> bool:
|
||||
"""Check if session is currently active session"""
|
||||
request: Request = self.context["request"]
|
||||
return request._request.session.session_key == instance.session_key
|
||||
|
||||
def get_user_agent(self, instance: AuthenticatedSession) -> UserAgentDict:
|
||||
"""Get parsed user agent"""
|
||||
return user_agent_parser.Parse(instance.last_user_agent)
|
||||
|
||||
def get_geo_ip(
|
||||
self, instance: AuthenticatedSession
|
||||
) -> Optional[GeoIPDict]: # pragma: no cover
|
||||
"""Get parsed user agent"""
|
||||
if not GEOIP_READER:
|
||||
return None
|
||||
try:
|
||||
city = GEOIP_READER.city(instance.last_ip)
|
||||
return {
|
||||
"continent": city.continent.code,
|
||||
"country": city.country.iso_code,
|
||||
"lat": city.location.latitude,
|
||||
"long": city.location.longitude,
|
||||
}
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
|
||||
model = AuthenticatedSession
|
||||
fields = [
|
||||
"uuid",
|
||||
"current",
|
||||
"user_agent",
|
||||
"geo_ip",
|
||||
"user",
|
||||
"last_ip",
|
||||
"last_user_agent",
|
||||
"last_used",
|
||||
"expires",
|
||||
]
|
||||
|
||||
|
||||
class AuthenticatedSessionViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
"""AuthenticatedSession Viewset"""
|
||||
|
||||
queryset = AuthenticatedSession.objects.all()
|
||||
serializer_class = AuthenticatedSessionSerializer
|
||||
search_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||
ordering = ["user__username"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
|
@ -0,0 +1,86 @@
|
|||
# Generated by Django 3.2.3 on 2021-05-29 22:14
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.utils.timezone import now
|
||||
|
||||
import authentik.core.models
|
||||
|
||||
|
||||
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX, SessionStore
|
||||
from django.core.cache import cache
|
||||
|
||||
AuthenticatedSession = apps.get_model("authentik_core", "AuthenticatedSession")
|
||||
User = apps.get_model("authentik_core", "user")
|
||||
|
||||
session_keys = cache.keys(KEY_PREFIX + "*")
|
||||
for key in session_keys:
|
||||
key = key.replace(KEY_PREFIX, "")
|
||||
store = SessionStore(key)
|
||||
data = store.load()
|
||||
if data == {} or "_auth_user_id" not in data:
|
||||
continue
|
||||
if (
|
||||
AuthenticatedSession.objects.using(db_alias)
|
||||
.filter(session_key=key)
|
||||
.exists()
|
||||
):
|
||||
continue
|
||||
users = User.objects.using(db_alias).filter(pk=data.get("_auth_user_id"))
|
||||
if not users.exists():
|
||||
continue
|
||||
AuthenticatedSession.objects.using(db_alias).create(
|
||||
session_key=key,
|
||||
user=users.first(),
|
||||
expires=data.get("_session_expiry", now()),
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0021_alter_application_slug"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AuthenticatedSession",
|
||||
fields=[
|
||||
(
|
||||
"expires",
|
||||
models.DateTimeField(
|
||||
default=authentik.core.models.default_token_duration
|
||||
),
|
||||
),
|
||||
("expiring", models.BooleanField(default=True)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("session_key", models.CharField(max_length=40)),
|
||||
("last_ip", models.TextField()),
|
||||
("last_user_agent", models.TextField(blank=True)),
|
||||
("last_used", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_sessions),
|
||||
]
|
|
@ -28,6 +28,7 @@ from authentik.flows.challenge import Challenge
|
|||
from authentik.flows.models import Flow
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
|
||||
from authentik.lib.utils.http import get_client_ip
|
||||
from authentik.managed.models import ManagedModel
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
|
||||
|
@ -452,3 +453,33 @@ class PropertyMapping(SerializerModel, ManagedModel):
|
|||
|
||||
verbose_name = _("Property Mapping")
|
||||
verbose_name_plural = _("Property Mappings")
|
||||
|
||||
|
||||
class AuthenticatedSession(ExpiringModel):
|
||||
"""Additional session class for authenticated users. Augments the standard django session
|
||||
to achieve the following:
|
||||
- Make it queryable by user
|
||||
- Have a direct connection to user objects
|
||||
- Allow users to view their own sessions and terminate them
|
||||
- Save structured and well-defined information.
|
||||
"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, primary_key=True)
|
||||
|
||||
session_key = models.CharField(max_length=40)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
last_ip = models.TextField()
|
||||
last_user_agent = models.TextField(blank=True)
|
||||
last_used = models.DateTimeField(auto_now=True)
|
||||
|
||||
@staticmethod
|
||||
def from_request(request: HttpRequest, user: User) -> "AuthenticatedSession":
|
||||
"""Create a new session from a http request"""
|
||||
return AuthenticatedSession(
|
||||
session_key=request.session.session_key,
|
||||
user=user,
|
||||
last_ip=get_client_ip(request),
|
||||
last_user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
||||
expires=request.session.get_expiry_date(),
|
||||
)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
"""authentik core signals"""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.core.cache import cache
|
||||
from django.core.signals import Signal
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.http.request import HttpRequest
|
||||
from prometheus_client import Gauge
|
||||
|
||||
# Arguments: user: User, password: str
|
||||
|
@ -13,6 +17,9 @@ GAUGE_MODELS = Gauge(
|
|||
"authentik_models", "Count of various objects", ["model_name", "app"]
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -33,3 +40,23 @@ def post_save_application(sender: type[Model], instance, created: bool, **_):
|
|||
# Also delete user application cache
|
||||
keys = cache.keys(user_app_cache_key("*"))
|
||||
cache.delete_many(keys)
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
# pylint: disable=unused-argument
|
||||
def user_logged_in_session(sender, request: HttpRequest, user: "User", **_):
|
||||
"""Create an AuthenticatedSession from request"""
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
|
||||
AuthenticatedSession.from_request(request, user).save()
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
# pylint: disable=unused-argument
|
||||
def user_logged_out_session(sender, request: HttpRequest, user: "User", **_):
|
||||
"""Delete AuthenticatedSession if it exists"""
|
||||
from authentik.core.models import AuthenticatedSession
|
||||
|
||||
AuthenticatedSession.objects.filter(
|
||||
session_key=request.session.session_key
|
||||
).delete()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
"""Test AuthenticatedSessions API"""
|
||||
from json import loads
|
||||
|
||||
from django.urls.base import reverse
|
||||
from django.utils.encoding import force_str
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
class TestAuthenticatedSessionsAPI(APITestCase):
|
||||
"""Test AuthenticatedSessions API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.get(username="akadmin")
|
||||
self.other_user = User.objects.create(username="normal-user")
|
||||
|
||||
def test_list(self):
|
||||
"""Test session list endpoint"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_non_admin_list(self):
|
||||
"""Test non-admin list"""
|
||||
self.client.force_login(self.other_user)
|
||||
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(force_str(response.content))
|
||||
self.assertEqual(body["pagination"]["count"], 1)
|
|
@ -149,7 +149,7 @@ class Event(ExpiringModel):
|
|||
request.session[SESSION_IMPERSONATE_USER]
|
||||
)
|
||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||
self.client_ip = get_client_ip(request) or "255.255.255.255"
|
||||
self.client_ip = get_client_ip(request)
|
||||
# Apply GeoIP Data, when enabled
|
||||
self.with_geoip()
|
||||
# If there's no app set, we get it from the requests too
|
||||
|
@ -158,7 +158,7 @@ class Event(ExpiringModel):
|
|||
self.save()
|
||||
return self
|
||||
|
||||
def with_geoip(self):
|
||||
def with_geoip(self): # pragma: no cover
|
||||
"""Apply GeoIP Data, when enabled"""
|
||||
if not GEOIP_READER:
|
||||
return
|
||||
|
@ -172,7 +172,7 @@ class Event(ExpiringModel):
|
|||
}
|
||||
if response.city.name:
|
||||
self.context["geo"]["city"] = response.city.name
|
||||
except GeoIP2Error as exc:
|
||||
except (GeoIP2Error, ValueError) as exc:
|
||||
LOGGER.warning("Failed to add geoIP Data to event", exc=exc)
|
||||
|
||||
def _set_prom_metrics(self):
|
||||
|
|
|
@ -47,7 +47,7 @@ outposts:
|
|||
|
||||
authentik:
|
||||
avatars: gravatar # gravatar or none
|
||||
geoip: ""
|
||||
geoip: "./GeoLite2-City.mmdb"
|
||||
# Optionally add links to the footer on the login page
|
||||
footer_links:
|
||||
- name: Documentation
|
||||
|
|
|
@ -5,9 +5,10 @@ from django.http import HttpRequest
|
|||
|
||||
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
|
||||
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
|
||||
DEFAULT_IP = "255.255.255.255"
|
||||
|
||||
|
||||
def _get_client_ip_from_meta(meta: dict[str, Any]) -> Optional[str]:
|
||||
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found"""
|
||||
headers = (
|
||||
|
@ -19,7 +20,7 @@ def _get_client_ip_from_meta(meta: dict[str, Any]) -> Optional[str]:
|
|||
if _header in meta:
|
||||
ips: list[str] = meta.get(_header).split(",")
|
||||
return ips[0].strip()
|
||||
return None
|
||||
return DEFAULT_IP
|
||||
|
||||
|
||||
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
||||
|
@ -37,7 +38,7 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
|||
return request.META[OUTPOST_REMOTE_IP_HEADER]
|
||||
|
||||
|
||||
def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]:
|
||||
def get_client_ip(request: Optional[HttpRequest]) -> str:
|
||||
"""Attempt to get the client's IP by checking common HTTP Headers.
|
||||
Returns none if no IP Could be found"""
|
||||
if request:
|
||||
|
@ -45,4 +46,4 @@ def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]:
|
|||
if override:
|
||||
return override
|
||||
return _get_client_ip_from_meta(request.META)
|
||||
return None
|
||||
return DEFAULT_IP
|
||||
|
|
|
@ -50,9 +50,7 @@ class PolicyEvaluator(BaseEvaluator):
|
|||
"""Update context based on http request"""
|
||||
# update website/docs/expressions/_objects.md
|
||||
# update website/docs/expressions/_functions.md
|
||||
self._context["ak_client_ip"] = ip_address(
|
||||
get_client_ip(request) or "255.255.255.255"
|
||||
)
|
||||
self._context["ak_client_ip"] = ip_address(get_client_ip(request))
|
||||
self._context["http_request"] = request
|
||||
|
||||
def handle_error(self, exc: Exception, expression_source: str):
|
||||
|
|
|
@ -30,7 +30,7 @@ class ReputationPolicy(Policy):
|
|||
return "ak-policy-reputation-form"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
remote_ip = get_client_ip(request.http_request) or "255.255.255.255"
|
||||
remote_ip = get_client_ip(request.http_request)
|
||||
passing = True
|
||||
if self.check_ip:
|
||||
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||
|
|
|
@ -17,7 +17,7 @@ LOGGER = get_logger()
|
|||
|
||||
def update_score(request: HttpRequest, username: str, amount: int):
|
||||
"""Update score for IP and User"""
|
||||
remote_ip = get_client_ip(request) or "255.255.255.255"
|
||||
remote_ip = get_client_ip(request)
|
||||
|
||||
# We only update the cache here, as its faster than writing to the DB
|
||||
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||
|
|
|
@ -36,7 +36,7 @@ class PolicyRequest:
|
|||
self.obj = None
|
||||
self.context = {}
|
||||
|
||||
def set_http_request(self, request: HttpRequest):
|
||||
def set_http_request(self, request: HttpRequest): # pragma: no cover
|
||||
"""Load data from HTTP request, including geoip when enabled"""
|
||||
self.http_request = request
|
||||
if not GEOIP_READER:
|
||||
|
@ -47,7 +47,7 @@ class PolicyRequest:
|
|||
return
|
||||
response = GEOIP_READER.city(client_ip)
|
||||
self.context["geoip"] = response
|
||||
except GeoIP2Error as exc:
|
||||
except (GeoIP2Error, ValueError) as exc:
|
||||
LOGGER.warning("failed to get geoip data", exc=exc)
|
||||
|
||||
def __str__(self):
|
||||
|
|
228
schema.yml
228
schema.yml
|
@ -1520,6 +1520,114 @@ paths:
|
|||
description: Bad request
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/authenticated_sessions/:
|
||||
get:
|
||||
operationId: core_authenticated_sessions_list
|
||||
description: AuthenticatedSession Viewset
|
||||
parameters:
|
||||
- in: query
|
||||
name: last_ip
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: last_user_agent
|
||||
schema:
|
||||
type: string
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
required: false
|
||||
in: query
|
||||
description: A page number within the paginated result set.
|
||||
schema:
|
||||
type: integer
|
||||
- name: page_size
|
||||
required: false
|
||||
in: query
|
||||
description: Number of results to return per page.
|
||||
schema:
|
||||
type: integer
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: user__username
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedAuthenticatedSessionList'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/authenticated_sessions/{uuid}/:
|
||||
get:
|
||||
operationId: core_authenticated_sessions_retrieve
|
||||
description: AuthenticatedSession Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this authenticated session.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthenticatedSession'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
delete:
|
||||
operationId: core_authenticated_sessions_destroy
|
||||
description: AuthenticatedSession Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this authenticated session.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/groups/:
|
||||
get:
|
||||
operationId: core_groups_list
|
||||
|
@ -15474,6 +15582,90 @@ components:
|
|||
If empty, user will not be able to configure this stage.
|
||||
required:
|
||||
- name
|
||||
AuthenticatedSession:
|
||||
type: object
|
||||
description: AuthenticatedSession Serializer
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
current:
|
||||
type: boolean
|
||||
readOnly: true
|
||||
user_agent:
|
||||
type: object
|
||||
properties:
|
||||
device:
|
||||
type: object
|
||||
properties:
|
||||
brand:
|
||||
type: string
|
||||
family:
|
||||
type: string
|
||||
model:
|
||||
type: string
|
||||
os:
|
||||
type: object
|
||||
properties:
|
||||
family:
|
||||
type: string
|
||||
major:
|
||||
type: string
|
||||
minor:
|
||||
type: string
|
||||
patch:
|
||||
type: string
|
||||
patch_minor:
|
||||
type: string
|
||||
user_agent:
|
||||
type: object
|
||||
properties:
|
||||
family:
|
||||
type: string
|
||||
major:
|
||||
type: string
|
||||
minor:
|
||||
type: string
|
||||
patch:
|
||||
type: string
|
||||
string:
|
||||
type: string
|
||||
readOnly: true
|
||||
geo_ip:
|
||||
type: object
|
||||
properties:
|
||||
continent:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
lat:
|
||||
type: number
|
||||
format: float
|
||||
long:
|
||||
type: number
|
||||
format: float
|
||||
nullable: true
|
||||
readOnly: true
|
||||
user:
|
||||
type: integer
|
||||
last_ip:
|
||||
type: string
|
||||
last_user_agent:
|
||||
type: string
|
||||
last_used:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
expires:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- current
|
||||
- geo_ip
|
||||
- last_ip
|
||||
- last_used
|
||||
- user
|
||||
- user_agent
|
||||
AuthenticatorDuoChallenge:
|
||||
type: object
|
||||
description: Duo Challenge
|
||||
|
@ -16013,6 +16205,7 @@ components:
|
|||
CapabilitiesEnum:
|
||||
enum:
|
||||
- can_save_media
|
||||
- can_geo_ip
|
||||
type: string
|
||||
CaptchaChallenge:
|
||||
type: object
|
||||
|
@ -18914,6 +19107,41 @@ components:
|
|||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedAuthenticatedSessionList:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
type: object
|
||||
properties:
|
||||
next:
|
||||
type: number
|
||||
previous:
|
||||
type: number
|
||||
count:
|
||||
type: number
|
||||
current:
|
||||
type: number
|
||||
total_pages:
|
||||
type: number
|
||||
start_index:
|
||||
type: number
|
||||
end_index:
|
||||
type: number
|
||||
required:
|
||||
- next
|
||||
- previous
|
||||
- count
|
||||
- current
|
||||
- total_pages
|
||||
- start_index
|
||||
- end_index
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthenticatedSession'
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedAuthenticatorDuoStageList:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { t } from "@lingui/macro";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { Table, TableColumn } from "../table/Table";
|
||||
|
||||
import "../forms/DeleteForm";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
import { CoreApi, AuthenticatedSession } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
|
||||
@customElement("ak-user-session-list")
|
||||
export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
|
||||
|
||||
@property()
|
||||
targetUser!: string;
|
||||
|
||||
apiEndpoint(page: number): Promise<AKResponse<AuthenticatedSession>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsList({
|
||||
userUsername: this.targetUser,
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
order = "-expires";
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Last IP`, "last_ip"),
|
||||
new TableColumn(t`Browser`, "user_agent"),
|
||||
new TableColumn(t`Device`, "user_agent"),
|
||||
new TableColumn(t`Expires`, "expires"),
|
||||
new TableColumn(""),
|
||||
];
|
||||
}
|
||||
|
||||
row(item: AuthenticatedSession): TemplateResult[] {
|
||||
return [
|
||||
html`${item.lastIp}`,
|
||||
html`${item.userAgent.userAgent?.family}`,
|
||||
html`${item.userAgent.os?.family}`,
|
||||
html`${item.expires?.toLocaleString()}`,
|
||||
html`
|
||||
<ak-forms-delete
|
||||
.obj=${item}
|
||||
objectLabel=${t`Session`}
|
||||
.delete=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({
|
||||
uuid: item.uuid || "",
|
||||
});
|
||||
}}>
|
||||
<button slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete Session`}
|
||||
</button>
|
||||
</ak-forms-delete>`,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ export class UserConsentList extends Table<UserConsent> {
|
|||
apiEndpoint(page: number): Promise<AKResponse<UserConsent>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreUserConsentList({
|
||||
user: this.userId,
|
||||
ordering: "expires",
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
|
|
|
@ -13,18 +13,19 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import AKGlobal from "../../authentik.css";
|
||||
|
||||
import "../../elements/forms/ModalForm";
|
||||
import "../../elements/buttons/ActionButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/charts/UserChart";
|
||||
import "../../elements/CodeMirror";
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/events/ObjectChangelog";
|
||||
import "../../elements/user/UserConsentList";
|
||||
import "../../elements/events/UserEvents";
|
||||
import "../../elements/forms/ModalForm";
|
||||
import "../../elements/oauth/UserCodeList";
|
||||
import "../../elements/oauth/UserRefreshList";
|
||||
import "../../elements/charts/UserChart";
|
||||
import "../../elements/PageHeader";
|
||||
import "../../elements/events/UserEvents";
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/user/SessionList";
|
||||
import "../../elements/user/UserConsentList";
|
||||
import "./UserForm";
|
||||
import { CoreApi, User } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
|
@ -176,6 +177,14 @@ export class UserViewPage extends LitElement {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section slot="page-sessions" data-tab-title="${t`Sessions`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-session-list targetUser=${this.user.username}>
|
||||
</ak-user-session-list>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section slot="page-events" data-tab-title="${t`User events`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
|
|
Reference in New Issue