-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
1,767 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "aidbox-2fa"] | ||
path = aidbox-2fa | ||
url = [email protected]:beda-software/aidbox-2fa.git |
Submodule aidbox-2fa
added at
88ac31
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
APP_INIT_CLIENT_ID=root | ||
APP_INIT_CLIENT_SECRET=secret | ||
APP_INIT_URL=http://devbox:8080 | ||
|
||
APP_ID=aidbox-2fa-app | ||
APP_SECRET=secret | ||
|
||
APP_URL=http://aidbox-2fa:8081 | ||
APP_PORT=8081 | ||
AIO_PORT=8081 | ||
AIO_HOST=0.0.0.0 | ||
|
||
TWO_FACTOR_ISSUER_NAME=MammoChat | ||
TWO_FACTOR_VALID_PAST_TOKENS_COUNT=5 | ||
TWO_FACTOR_WEBHOOK_URL="http://devbox:8080/webhook/two-factor-confirmation" | ||
TWO_FACTOR_WEBHOOK_AUTHORIZATION="Basic cm9vdDpzZWNyZXQ=" # root:secret |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
template: |- | ||
<style type="text/css"> | ||
</style> | ||
<div> | ||
{{ body }} | ||
</div> | ||
id: email-layout | ||
|
6 changes: 6 additions & 0 deletions
6
resources/seeds/NotificationTemplate/reset-user-password.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
subject: Reset password | ||
template: |- | ||
<p>Dear {{user.name.givenName}},<br /> | ||
To reset your password click this </p> | ||
<a href="{{confirm-href}}">link</a> | ||
id: reset-user-password |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
subject: Verify two factor | ||
template: |- | ||
<p>Dear {{user.name.givenName}},<br /> | ||
Your code is {{token}} </p> | ||
id: verify-two-factor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
FROM python:3.12 | ||
|
||
RUN addgroup --gid 1000 dockeruser | ||
RUN adduser --disabled-login --uid 1000 --gid 1000 dockeruser | ||
RUN mkdir -p /app/ | ||
RUN chown -R dockeruser:dockeruser /app/ | ||
|
||
RUN pip install poetry | ||
|
||
USER dockeruser | ||
|
||
COPY . /app | ||
WORKDIR /app | ||
|
||
RUN poetry install | ||
|
||
CMD ["poetry", "run", "gunicorn", "app.main:application", "--bind", "0.0.0.0:8081", "--worker-class", "aiohttp.GunicornWebWorker", "--reload"] |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import app.aidbox.operations | ||
import app.aidbox.subscriptions | ||
import app.aidbox.notification | ||
import app.aidbox.two_factor | ||
|
||
from .sdk import sdk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import logging | ||
|
||
from aidbox_python_sdk.aidboxpy import AsyncAidboxClient | ||
from jinja2 import Environment, Undefined | ||
from jinja2.ext import Extension | ||
from premailer import transform | ||
|
||
from app.config import emr as emr_config | ||
|
||
async def send_email( | ||
client: AsyncAidboxClient, to, template_id, payload, attachments=None, *, save=True | ||
): | ||
notification = client.resource( | ||
"Notification", | ||
provider=emr_config.EMAIL_PROVIDER, | ||
providerData={ | ||
"fromApp": True, | ||
"type": "email", | ||
"to": to, | ||
"payload": payload, | ||
"template": {"resourceType": "NotificationTemplate", "id": template_id}, | ||
"attachments": attachments or [], | ||
}, | ||
) | ||
if save: | ||
await notification.save() | ||
return notification | ||
|
||
|
||
class SilentUndefined(Undefined): | ||
def _fail_with_undefined_error(self, *args, **kwargs): # type: ignore | ||
return None | ||
|
||
|
||
class RenderBlocksExtension(Extension): | ||
def __init__(self, environment): | ||
super().__init__(environment) | ||
environment.extend(render_blocks=[]) | ||
|
||
def filter_stream(self, stream): | ||
block_level = 0 | ||
skip_level = 0 | ||
in_endblock = False | ||
|
||
for token in stream: | ||
if token.type == "block_begin": | ||
if stream.current.value == "block": | ||
block_level += 1 | ||
if stream.look().value not in self.environment.render_blocks: # type: ignore | ||
skip_level = block_level | ||
|
||
if token.value == "endblock": | ||
in_endblock = True | ||
|
||
if skip_level == 0: | ||
yield token | ||
|
||
if token.type == "block_end": | ||
if in_endblock: | ||
in_endblock = False | ||
block_level -= 1 | ||
|
||
if skip_level == block_level + 1: | ||
skip_level = 0 | ||
|
||
|
||
jinja_env = Environment(undefined=SilentUndefined) | ||
|
||
jinja_subject_env = Environment(undefined=SilentUndefined, extensions=[RenderBlocksExtension]) | ||
jinja_subject_env.render_blocks = ["subject"] # type: ignore | ||
|
||
jinja_body_env = Environment(undefined=SilentUndefined, extensions=[RenderBlocksExtension]) | ||
jinja_body_env.render_blocks = ["body"] # type: ignore | ||
|
||
|
||
class SendNotificationExceptionError(Exception): | ||
pass | ||
|
||
|
||
async def send_console(to, subject, body, attachments=None): | ||
logging.info( | ||
"New notification:\nTo: %s\nSubject: %s\n%s\nattachments: %s", | ||
to, | ||
subject, | ||
body, | ||
attachments or [], | ||
) | ||
|
||
|
||
providers = { | ||
"console": send_console, | ||
} | ||
|
||
|
||
async def notification_sub(app, action, resource, _previous_resource): | ||
sdk_settings = app["settings"] | ||
client = app["client"] | ||
if action == "create": | ||
provider_data = resource["providerData"] | ||
|
||
# Skip processing for non-app notifications | ||
if not provider_data.get("fromApp"): | ||
return | ||
|
||
notification_type = provider_data.get("type") | ||
if notification_type == "email": | ||
payload = { | ||
**provider_data.get("payload", {}), | ||
"frontend_url": sdk_settings.FRONTEND_URL, | ||
# "backend_url": sdk_settings.backend_public_url, | ||
# "current_date": format_fhir_date(get_now()), | ||
} | ||
|
||
template = await provider_data["template"].to_resource() | ||
subject_template = jinja_subject_env.from_string(template["subject"]) | ||
body_template = jinja_body_env.from_string(template["template"]) | ||
subject = subject_template.render(payload) | ||
body = body_template.render(payload) | ||
layout = await client.resources("NotificationTemplate").get(id="email-layout") | ||
body = transform( | ||
jinja_env.from_string(layout["template"]).render({"body": body, **payload}) | ||
) | ||
props = { | ||
"subject": subject.strip(), | ||
"body": body.strip(), | ||
"to": provider_data["to"], | ||
} | ||
if "attachments" in provider_data: | ||
props["attachments"] = provider_data["attachments"] | ||
elif notification_type == "sms": | ||
props = { | ||
"to": provider_data["to"], | ||
"body": provider_data["body"].strip(), | ||
} | ||
else: | ||
raise Exception("Notification type `%s` is not supported", notification_type) | ||
|
||
provider = resource["provider"] | ||
|
||
send_fn = providers.get(provider) | ||
if not send_fn: | ||
logging.warning("Unhandled notification for provider %s", provider) | ||
return | ||
|
||
try: | ||
await send_fn(**props) # type: ignore | ||
|
||
resource["status"] = "delivered" | ||
await resource.save() | ||
except SendNotificationExceptionError as exc: | ||
logging.debug(exc) | ||
resource["status"] = "error" | ||
await resource.save() | ||
except Exception as exc: | ||
logging.exception(exc) | ||
resource["status"] = "failure" | ||
await resource.save() | ||
raise |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from aidbox_python_sdk.sdk import SDK | ||
|
||
from .settings import settings | ||
|
||
sdk = SDK(settings) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from aidbox_python_sdk.settings import Required | ||
from aidbox_python_sdk.settings import Settings as AidboxSettings | ||
|
||
from app.config import aidbox, emr | ||
|
||
|
||
class Settings(AidboxSettings): | ||
APP_ID = Required(v_type=str) | ||
FRONTEND_URL = Required(v_type=str) | ||
APP_INIT_URL = Required(v_type=str) | ||
|
||
|
||
settings = Settings( | ||
APP_ID=aidbox.APP_ID, | ||
FRONTEND_URL=emr.FRONTEND_URL, | ||
APP_INIT_URL=aidbox.APP_INIT_URL, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import uuid | ||
|
||
from app.aidbox.sdk import sdk | ||
from app.config.emr import FRONTEND_URL | ||
from app.aidbox.notification import notification_sub | ||
|
||
@sdk.subscription("User") | ||
async def user_created(event, request): | ||
aidbox = request.app["client"] | ||
if event["action"] == "create": | ||
user = aidbox.resource("User", **event["resource"]) | ||
if user["data"].get("resetPassword", False): | ||
reset_token = uuid.uuid4() | ||
user["data"]["reset_token"] = str(reset_token) | ||
await user.save() | ||
notification = aidbox.resource( | ||
"Notification", | ||
**{ | ||
"provider": "smtp-provider", | ||
"providerData": { | ||
"to": user["email"], | ||
"subject": "Password reset", | ||
"template": { | ||
"id": "reset-user-password", | ||
"resourceType": "NotificationTemplate", | ||
}, | ||
"payload": { | ||
"user": user.serialize(), | ||
"confirm-href": f"{FRONTEND_URL}/reset-password/{reset_token}", | ||
}, | ||
}, | ||
}, | ||
) | ||
await notification.save() | ||
|
||
|
||
@sdk.subscription("Notification") | ||
async def notification_handler(event, request): | ||
aidbox = request.app["client"] | ||
notification = aidbox.resource("Notification", **event["resource"]) | ||
|
||
await notification_sub(request.app, event["action"], notification, None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import logging | ||
|
||
from aiohttp import web | ||
|
||
from app.aidbox.notification import send_email | ||
from app.aidbox.sdk import sdk | ||
|
||
from aidbox_python_sdk.types import SDKOperation, SDKOperationRequest | ||
from aidbox_python_sdk import app_keys as ak | ||
from aidbox_python_sdk.aidboxpy import AsyncAidboxClient, AsyncAidboxResource | ||
|
||
@sdk.operation(["POST"], ["webhook", "two-factor-confirmation"]) | ||
async def auth_webhook_two_factor_confirmation_op(_operation: SDKOperation, request: SDKOperationRequest): | ||
client = request["app"][ak.client] | ||
user = client.resource("User", **request["resource"]["user"]) | ||
token = request["resource"]["token"] | ||
await send_confirmation_token(client, user, token) | ||
return web.json_response({}) | ||
|
||
|
||
async def send_confirmation_token(client: AsyncAidboxClient, user: AsyncAidboxResource, token: str): | ||
logging.info( | ||
"OTP for user {email}: {token}".format(email=user["email"], token=token) | ||
) | ||
|
||
await send_email(client, user["email"], "verify-two-factor", { | ||
"token": token, | ||
"user": user.serialize() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
def get_error_payload(message, *, code): | ||
return {"error": code, "error_description": message} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from os import environ | ||
|
||
APP_INIT_URL = environ["APP_INIT_URL"] | ||
APP_INIT_CLIENT_ID = environ["APP_INIT_CLIENT_ID"] | ||
APP_INIT_CLIENT_SECRET = environ["APP_INIT_CLIENT_SECRET"] | ||
APP_ID=environ["APP_ID"] |
Oops, something went wrong.