Invite links prototype working
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
480cd4a4fe
commit
80dd93823e
11
README.md
11
README.md
@ -1,5 +1,11 @@
|
||||
# keycloak-collective-portal
|
||||
|
||||
> **WARNING**: this software is in a pre-alpha quality state and is an initial
|
||||
> prototype. It is being developed within the context of
|
||||
> [lumbung.space](https://lumbung.space/) and maybe have hard-coded values and
|
||||
> configuration specifically for that environment. If the idea of this software
|
||||
> sounds interesting to you, please let us know on the issue tracker!
|
||||
|
||||
[![Build Status](https://drone.autonomic.zone/api/badges/autonomic-cooperative/keycloak-collective-portal/status.svg?ref=refs/heads/main)](https://drone.autonomic.zone/autonomic-cooperative/keycloak-collective-portal)
|
||||
|
||||
> Community Keycloak SSO user management
|
||||
@ -42,9 +48,12 @@ your technology stack.
|
||||
|
||||
## Hacking
|
||||
|
||||
It's a [FastAPI](https://fastapi.tiangolo.com/) application.
|
||||
It's a [FastAPI](https://fastapi.tiangolo.com/) application. Currently being
|
||||
developed with Python 3.9. Once we move out of the prototype stage, more
|
||||
version compatability will be offered.
|
||||
|
||||
```
|
||||
$ docker run -p 6379:6379 -d redis:6-alpine
|
||||
$ set -a && source .envrc && set +a
|
||||
$ make
|
||||
```
|
||||
|
@ -9,10 +9,11 @@ from uuid import uuid4
|
||||
import httpx
|
||||
from aioredis import create_redis_pool
|
||||
from authlib.integrations.starlette_client import OAuth, OAuthError
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request
|
||||
from fastapi import Depends, FastAPI, Form, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from humanize import naturaldelta
|
||||
from keycloak import KeycloakAdmin
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
@ -35,17 +36,6 @@ app = FastAPI(docs_url=None, redoc_url=None)
|
||||
app.add_middleware(SessionMiddleware, secret_key=APP_SECRET_KEY)
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
oauth = OAuth()
|
||||
oauth.register(
|
||||
name="keycloak",
|
||||
client_kwargs={"scope": "openid profile email"},
|
||||
client_id=KEYCLOAK_CLIENT_ID,
|
||||
client_secret=KEYCLOAK_CLIENT_SECRET,
|
||||
authorize_url=f"{BASE_URL}/auth",
|
||||
access_token_url=f"{BASE_URL}/token",
|
||||
jwks_uri=f"{BASE_URL}/certs",
|
||||
)
|
||||
|
||||
|
||||
class RequiresLoginException(Exception):
|
||||
pass
|
||||
@ -74,8 +64,18 @@ async def get_user(request: Request):
|
||||
|
||||
|
||||
async def get_invites(request: Request, user=Depends(get_user)):
|
||||
if not user:
|
||||
idx, invites = b"0", {}
|
||||
while idx:
|
||||
idx, username = await app.state.redis.scan(idx)
|
||||
invites[username[0]] = json.loads(
|
||||
await app.state.redis.get(username[0])
|
||||
)
|
||||
return invites
|
||||
|
||||
username = user["preferred_username"]
|
||||
invites = await app.state.redis.get(username)
|
||||
|
||||
if invites:
|
||||
humanised = []
|
||||
for invite in json.loads(invites):
|
||||
@ -85,6 +85,7 @@ async def get_invites(request: Request, user=Depends(get_user)):
|
||||
)
|
||||
humanised.append(invite)
|
||||
return humanised
|
||||
|
||||
return []
|
||||
|
||||
|
||||
@ -94,6 +95,25 @@ async def starup_event():
|
||||
f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}?encoding=utf-8"
|
||||
)
|
||||
|
||||
oauth = OAuth()
|
||||
oauth.register(
|
||||
name="keycloak",
|
||||
client_kwargs={"scope": "openid profile email"},
|
||||
client_id=KEYCLOAK_CLIENT_ID,
|
||||
client_secret=KEYCLOAK_CLIENT_SECRET,
|
||||
authorize_url=f"{BASE_URL}/auth",
|
||||
access_token_url=f"{BASE_URL}/token",
|
||||
jwks_uri=f"{BASE_URL}/certs",
|
||||
)
|
||||
app.state.oauth = oauth
|
||||
|
||||
app.state.keycloak = KeycloakAdmin(
|
||||
server_url=f"https://{KEYCLOAK_DOMAIN}/auth/",
|
||||
realm_name=KEYCLOAK_REALM,
|
||||
client_secret_key=KEYCLOAK_CLIENT_SECRET,
|
||||
verify=True,
|
||||
)
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
@ -119,17 +139,19 @@ async def login(request: Request):
|
||||
@app.get("/login/keycloak")
|
||||
async def login_keycloak(request: Request):
|
||||
redirect_uri = request.url_for("auth_keycloak")
|
||||
return await oauth.keycloak.authorize_redirect(request, redirect_uri)
|
||||
return await app.state.oauth.keycloak.authorize_redirect(
|
||||
request, redirect_uri
|
||||
)
|
||||
|
||||
|
||||
@app.get("/auth/keycloak")
|
||||
async def auth_keycloak(request: Request):
|
||||
try:
|
||||
token = await oauth.keycloak.authorize_access_token(request)
|
||||
token = await app.state.oauth.keycloak.authorize_access_token(request)
|
||||
except Exception as exc:
|
||||
return HTMLResponse(f"<p>{exc} (<a href='{home}'>home</a>)</p>")
|
||||
|
||||
user = await oauth.keycloak.parse_id_token(request, token)
|
||||
user = await app.state.oauth.keycloak.parse_id_token(request, token)
|
||||
request.session["user"] = dict(user)
|
||||
|
||||
return RedirectResponse(request.url_for("home"))
|
||||
@ -152,7 +174,8 @@ async def invite_keycloak_create(
|
||||
request: Request, user=Depends(get_user), invites=Depends(get_invites)
|
||||
):
|
||||
invites.append({"link": str(uuid4()), "time": str(dt.now())})
|
||||
app.state.redis.set(user["preferred_username"], json.dumps(invites))
|
||||
await app.state.redis.set(user["preferred_username"], json.dumps(invites))
|
||||
print(invites, json.dumps(invites))
|
||||
return RedirectResponse(request.url_for("home"))
|
||||
|
||||
|
||||
@ -160,5 +183,57 @@ async def invite_keycloak_create(
|
||||
async def invite_keycloak_delete(
|
||||
request: Request, user=Depends(get_user), invites=Depends(get_invites)
|
||||
):
|
||||
invite =request.query_params.get("invite")
|
||||
# TODO
|
||||
invite_to_delete = request.query_params.get("invite")
|
||||
purged = [i for i in invites if i["link"] != invite_to_delete]
|
||||
await app.state.redis.set(user["preferred_username"], json.dumps(purged))
|
||||
return RedirectResponse(request.url_for("home"))
|
||||
|
||||
|
||||
@app.get("/register/{invite}")
|
||||
async def register_invite(
|
||||
request: Request, invite: str, invites=Depends(get_invites)
|
||||
):
|
||||
matching, username = False, None
|
||||
for username in invites:
|
||||
if invite in [x["link"] for x in invites[username]]:
|
||||
matching = True
|
||||
username = username
|
||||
|
||||
if not matching:
|
||||
return RedirectResponse(request.url_for("login"))
|
||||
|
||||
context = {"request": request, "username": username}
|
||||
return templates.TemplateResponse("register.html", context=context)
|
||||
|
||||
|
||||
@app.post("/form/keycloak/register")
|
||||
def form_keycloak_register(
|
||||
request: Request,
|
||||
first_name: str = Form(...),
|
||||
last_name: str = Form(...),
|
||||
username: str = Form(...),
|
||||
email: str = Form(...),
|
||||
password: str = Form(...),
|
||||
):
|
||||
user_id = app.state.keycloak.create_user(
|
||||
{
|
||||
"email": email,
|
||||
"username": username,
|
||||
"enabled": True,
|
||||
"firstName": first_name,
|
||||
"lastName": last_name,
|
||||
"credentials": [
|
||||
{
|
||||
"value": password,
|
||||
"type": "password",
|
||||
}
|
||||
],
|
||||
"realmRoles": [
|
||||
"user_default",
|
||||
],
|
||||
}
|
||||
)
|
||||
app.state.keycloak.send_verify_email(user_id=user_id)
|
||||
|
||||
context = {"request": request, "success": True}
|
||||
return templates.TemplateResponse("register.html", context=context)
|
||||
|
181
poetry.lock
generated
181
poetry.lock
generated
@ -109,6 +109,14 @@ python-versions = "*"
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "chardet"
|
||||
version = "4.0.0"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.1"
|
||||
@ -147,6 +155,21 @@ sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.17.0"
|
||||
description = "ECDSA cryptographic signature library (pure python)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.9.0"
|
||||
|
||||
[package.extras]
|
||||
gmpy = ["gmpy"]
|
||||
gmpy2 = ["gmpy2"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.65.2"
|
||||
@ -252,11 +275,11 @@ tests = ["freezegun", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.2"
|
||||
version = "2.10"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
@ -342,6 +365,14 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.4.8"
|
||||
description = "ASN.1 types and codecs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.7.0"
|
||||
@ -392,6 +423,47 @@ python-versions = "*"
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.3.0"
|
||||
description = "JOSE implementation in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
ecdsa = "!=0.15"
|
||||
pyasn1 = "*"
|
||||
rsa = "*"
|
||||
|
||||
[package.extras]
|
||||
cryptography = ["cryptography (>=3.4.0)"]
|
||||
pycrypto = ["pycrypto (>=2.6.0,<2.7.0)", "pyasn1"]
|
||||
pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"]
|
||||
|
||||
[[package]]
|
||||
name = "python-keycloak"
|
||||
version = "0.25.0"
|
||||
description = "python-keycloak is a Python package providing access to the Keycloak API."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
python-jose = ">=1.4.0"
|
||||
requests = ">=2.20.0"
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.5"
|
||||
description = "A streaming multipart parser for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "5.4.1"
|
||||
@ -408,6 +480,24 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.25.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
chardet = ">=3.0.2,<5"
|
||||
idna = ">=2.5,<3"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
@ -422,6 +512,25 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||
[package.extras]
|
||||
idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.7.2"
|
||||
description = "Pure-Python RSA implementation"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5, <4"
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
@ -457,6 +566,19 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.5"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.14.0"
|
||||
@ -512,7 +634,7 @@ python-versions = ">=3.6.1"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "0ba44935dd5a68706610677a7e54fa3ebcd6f5a68edd05a1e1ecb8dbd9ca5947"
|
||||
content-hash = "6df09118b73a6b5e1a00cd4bc78d47d4b35faec4af719bd10f9f8a84bdd57ef0"
|
||||
|
||||
[metadata.files]
|
||||
aioredis = [
|
||||
@ -598,6 +720,10 @@ cffi = [
|
||||
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
|
||||
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
||||
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
||||
@ -620,6 +746,10 @@ cryptography = [
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
]
|
||||
ecdsa = [
|
||||
{file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
|
||||
{file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"},
|
||||
]
|
||||
fastapi = [
|
||||
{file = "fastapi-0.65.2-py3-none-any.whl", hash = "sha256:39569a18914075b2f1aaa03bcb9dc96a38e0e5dabaf3972e088c9077dfffa379"},
|
||||
{file = "fastapi-0.65.2.tar.gz", hash = "sha256:8359e55d8412a5571c0736013d90af235d6949ec4ce978e9b63500c8f4b6f714"},
|
||||
@ -705,8 +835,8 @@ humanize = [
|
||||
{file = "humanize-3.7.1.tar.gz", hash = "sha256:b8e7878f3063174b212bb82b9e5bee3b24bc47931e44df0bd34bcb1d8e0acf2f"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
|
||||
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"},
|
||||
@ -793,6 +923,21 @@ pathspec = [
|
||||
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
|
||||
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
|
||||
]
|
||||
pyasn1 = [
|
||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
|
||||
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
|
||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
|
||||
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
|
||||
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
|
||||
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
|
||||
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
|
||||
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
|
||||
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
|
||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
|
||||
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
|
||||
@ -833,6 +978,16 @@ python-dotenv = [
|
||||
{file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"},
|
||||
{file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"},
|
||||
]
|
||||
python-jose = [
|
||||
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
|
||||
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
|
||||
]
|
||||
python-keycloak = [
|
||||
{file = "python-keycloak-0.25.0.tar.gz", hash = "sha256:d02a7a4ed609583587482eacfdce409a00b633dff04ccf1cb3d478e1f0c50529"},
|
||||
]
|
||||
python-multipart = [
|
||||
{file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||
@ -907,10 +1062,22 @@ regex = [
|
||||
{file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"},
|
||||
{file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
|
||||
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
rsa = [
|
||||
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
|
||||
{file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
@ -928,6 +1095,10 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
|
||||
{file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"},
|
||||
{file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"},
|
||||
]
|
||||
uvicorn = [
|
||||
{file = "uvicorn-0.14.0-py3-none-any.whl", hash = "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae"},
|
||||
{file = "uvicorn-0.14.0.tar.gz", hash = "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"},
|
||||
|
@ -15,6 +15,8 @@ Authlib = "^0.15.4"
|
||||
httpx = "^0.18.1"
|
||||
aioredis = "^1.3.1"
|
||||
humanize = "^3.7.1"
|
||||
python-multipart = "^0.0.5"
|
||||
python-keycloak = "^0.25.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^21.6b0"
|
||||
|
@ -10,12 +10,12 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>Link</th>
|
||||
<th>Validity </th>
|
||||
<th>Validity</th>
|
||||
<th>Operations</th>
|
||||
</tr>
|
||||
{% for invite in invites %}
|
||||
<tr>
|
||||
<td> <a class="invite" href="{{ url_for('home') }}{{ invite.link }}">{{ url_for('home') }}{{ invite.link }}</a> </td>
|
||||
<td> <a class="invite" href="{{ url_for('register_invite', invite=invite.link) }}">{{ url_for('register_invite', invite=invite.link) }}</a> </td>
|
||||
<td> {{ invite.human_time }} </td>
|
||||
<td> <a href="{{ url_for('invite_keycloak_delete') }}?invite={{ invite.link }}">delete</a> </td>
|
||||
</tr>
|
||||
|
44
templates/register.html
Normal file
44
templates/register.html
Normal file
@ -0,0 +1,44 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Register</title>
|
||||
<style>
|
||||
input {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
You've been invited by {{ username }} to register an account!
|
||||
</p>
|
||||
|
||||
<form method="post" action="{{ url_for('form_keycloak_register') }}">
|
||||
<label for="first_name">First name:</label>
|
||||
<input type="text" name="first_name" />
|
||||
|
||||
<label for="last_name">Last name:</label>
|
||||
<input type="text" name="last_name" />
|
||||
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" name="username" />
|
||||
|
||||
<label for="email">Email:</label>
|
||||
<input type="text" name="email" />
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" name="password" />
|
||||
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
||||
{% if success %}
|
||||
<p>
|
||||
An email verification mail has been sent to {{ email }}. Please check your
|
||||
mail shortly to verify account and set your account password. Please also
|
||||
check your spam inbox in case it ends up in there.
|
||||
</p>
|
||||
{% elif failure %}
|
||||
<p>Something went wrong, oops! Please contact the system administrator.</p>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user