From 17cc2ddbde8d6a091bd57a370b9123e57bcf2af0 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Fri, 3 Jan 2025 22:47:23 +0000
Subject: [PATCH 01/10] Create a separate staff schema based on email domain
and add support for impersonation header
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
.idea/dataSources.xml | 2 +-
auth_volumes/kratos/identity.schema.json | 1 -
auth_volumes/kratos/kratos.yml | 4 +-
auth_volumes/kratos/staff.schema.json | 49 ++++++++++++++++++++++++
auth_volumes/oathkeeper/oathkeeper.yml | 5 +++
5 files changed, 58 insertions(+), 3 deletions(-)
create mode 100644 auth_volumes/kratos/staff.schema.json
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index d52b407b..db9e40be 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -20,7 +20,7 @@
sqlite.xerial
true
org.sqlite.JDBC
- jdbc:sqlite:$PROJECT_DIR$/auth/kratos/db.sqlite
+ jdbc:sqlite:$PROJECT_DIR$/auth_volumes/kratos/db.sqlite
diff --git a/auth_volumes/kratos/identity.schema.json b/auth_volumes/kratos/identity.schema.json
index 1a137875..d71cc3f1 100644
--- a/auth_volumes/kratos/identity.schema.json
+++ b/auth_volumes/kratos/identity.schema.json
@@ -1,5 +1,4 @@
{
- "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
diff --git a/auth_volumes/kratos/kratos.yml b/auth_volumes/kratos/kratos.yml
index ad9d1f04..22c7f5f2 100644
--- a/auth_volumes/kratos/kratos.yml
+++ b/auth_volumes/kratos/kratos.yml
@@ -107,8 +107,10 @@ hashers:
cost: 8
identity:
- default_schema_id: default
+ default_schema_id: staff
schemas:
+ - id: staff
+ url: file:///etc/config/kratos/staff.schema.json
- id: default
url: file:///etc/config/kratos/identity.schema.json
diff --git a/auth_volumes/kratos/staff.schema.json b/auth_volumes/kratos/staff.schema.json
new file mode 100644
index 00000000..f050843d
--- /dev/null
+++ b/auth_volumes/kratos/staff.schema.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Staff",
+ "type": "object",
+ "properties": {
+ "traits": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email",
+ "title": "E-Mail",
+ "minLength": 3,
+ "pattern": "^.*@staffdomain\\.com$",
+ "ory.sh/kratos": {
+ "credentials": {
+ "password": {
+ "identifier": true
+ }
+ },
+ "verification": {
+ "via": "email"
+ },
+ "recovery": {
+ "via": "email"
+ }
+ }
+ },
+ "name": {
+ "type": "object",
+ "properties": {
+ "first": {
+ "title": "First Name",
+ "type": "string"
+ },
+ "last": {
+ "title": "Last Name",
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": [
+ "email"
+ ],
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/auth_volumes/oathkeeper/oathkeeper.yml b/auth_volumes/oathkeeper/oathkeeper.yml
index 26137b63..c94a87da 100644
--- a/auth_volumes/oathkeeper/oathkeeper.yml
+++ b/auth_volumes/oathkeeper/oathkeeper.yml
@@ -96,5 +96,10 @@ mutators:
jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json
claims: |
{
+ {{ if eq .Extra.identity.schema_id "staff" }}
+ {{ if .MatchContext.Header.Get "x-impersonate" }}
+ "impersonate": {{ .MatchContext.Header.Get "x-impersonate" | toJson }},
+ {{ end }}
+ {{ end }}
"session": {{ .Extra | toJson }}
}
From bd494da76c882d12b59edb3d219d646069d6ead2 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Fri, 3 Jan 2025 23:36:09 +0000
Subject: [PATCH 02/10] Add example web_hook configuration after registration
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/kratos/kratos.yml | 17 ++++++++++++++++-
auth_volumes/kratos/user_registered.jsonnet | 7 +++++++
src/http_app/routes/__init__.py | 3 ++-
src/http_app/routes/user_registered_hook.py | 11 +++++++++++
4 files changed, 36 insertions(+), 2 deletions(-)
create mode 100644 auth_volumes/kratos/user_registered.jsonnet
create mode 100644 src/http_app/routes/user_registered_hook.py
diff --git a/auth_volumes/kratos/kratos.yml b/auth_volumes/kratos/kratos.yml
index 22c7f5f2..55671edd 100644
--- a/auth_volumes/kratos/kratos.yml
+++ b/auth_volumes/kratos/kratos.yml
@@ -85,6 +85,21 @@ selfservice:
password:
hooks:
- hook: session
+ - hook: web_hook
+ config:
+ url: http://dev:8000/user_registered/
+ method: "POST"
+# headers: { }
+ # https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating
+ body: file:///etc/config/kratos/user_registered.jsonnet
+ can_interrupt: false
+ emit_analytics_event: false
+# auth:
+# type: api_key
+# config:
+# name: ""
+# value: ""
+# in: header
# - hook: show_verification_ui
log:
@@ -107,7 +122,7 @@ hashers:
cost: 8
identity:
- default_schema_id: staff
+ default_schema_id: default
schemas:
- id: staff
url: file:///etc/config/kratos/staff.schema.json
diff --git a/auth_volumes/kratos/user_registered.jsonnet b/auth_volumes/kratos/user_registered.jsonnet
new file mode 100644
index 00000000..461429fa
--- /dev/null
+++ b/auth_volumes/kratos/user_registered.jsonnet
@@ -0,0 +1,7 @@
+function(ctx) {
+ user_id: ctx.identity.id,
+ traits: {
+ email: ctx.identity.traits.email,
+ name: ctx.identity.traits.name,
+ }
+}
diff --git a/src/http_app/routes/__init__.py b/src/http_app/routes/__init__.py
index 0ca50d64..f914c039 100644
--- a/src/http_app/routes/__init__.py
+++ b/src/http_app/routes/__init__.py
@@ -1,6 +1,6 @@
from fastapi import FastAPI
-from http_app.routes import api, events, graphql, hello, ping
+from http_app.routes import api, events, graphql, hello, ping, user_registered_hook
def init_routes(app: FastAPI) -> None:
@@ -8,4 +8,5 @@ def init_routes(app: FastAPI) -> None:
app.include_router(ping.router)
app.include_router(hello.router)
app.include_router(events.router)
+ app.include_router(user_registered_hook.router)
app.include_router(graphql.router, prefix="/graphql")
diff --git a/src/http_app/routes/user_registered_hook.py b/src/http_app/routes/user_registered_hook.py
new file mode 100644
index 00000000..16f32f2a
--- /dev/null
+++ b/src/http_app/routes/user_registered_hook.py
@@ -0,0 +1,11 @@
+import logging
+
+from fastapi import APIRouter, Request
+
+router = APIRouter(prefix="/user_registered")
+
+
+@router.post("/")
+async def user_registered(request: Request):
+ logging.info("User registered", extra={"body": await request.json()})
+ return {"user_registered": "OK"}
From 80c9f14066253173603f531cd1d69eb036020cf1 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Fri, 3 Jan 2025 23:57:22 +0000
Subject: [PATCH 03/10] Remove separate staff schema
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/kratos/kratos.yml | 2 --
auth_volumes/kratos/staff.schema.json | 49 ---------------------------
2 files changed, 51 deletions(-)
delete mode 100644 auth_volumes/kratos/staff.schema.json
diff --git a/auth_volumes/kratos/kratos.yml b/auth_volumes/kratos/kratos.yml
index 55671edd..387e49e9 100644
--- a/auth_volumes/kratos/kratos.yml
+++ b/auth_volumes/kratos/kratos.yml
@@ -124,8 +124,6 @@ hashers:
identity:
default_schema_id: default
schemas:
- - id: staff
- url: file:///etc/config/kratos/staff.schema.json
- id: default
url: file:///etc/config/kratos/identity.schema.json
diff --git a/auth_volumes/kratos/staff.schema.json b/auth_volumes/kratos/staff.schema.json
deleted file mode 100644
index f050843d..00000000
--- a/auth_volumes/kratos/staff.schema.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "title": "Staff",
- "type": "object",
- "properties": {
- "traits": {
- "type": "object",
- "properties": {
- "email": {
- "type": "string",
- "format": "email",
- "title": "E-Mail",
- "minLength": 3,
- "pattern": "^.*@staffdomain\\.com$",
- "ory.sh/kratos": {
- "credentials": {
- "password": {
- "identifier": true
- }
- },
- "verification": {
- "via": "email"
- },
- "recovery": {
- "via": "email"
- }
- }
- },
- "name": {
- "type": "object",
- "properties": {
- "first": {
- "title": "First Name",
- "type": "string"
- },
- "last": {
- "title": "Last Name",
- "type": "string"
- }
- }
- }
- },
- "required": [
- "email"
- ],
- "additionalProperties": false
- }
- }
-}
From b3aeb44de341cec57a48e3ac5bd7df1bf62bdaa5 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Fri, 3 Jan 2025 23:59:57 +0000
Subject: [PATCH 04/10] Remove checks related to identity schema
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/oathkeeper/oathkeeper.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/auth_volumes/oathkeeper/oathkeeper.yml b/auth_volumes/oathkeeper/oathkeeper.yml
index c94a87da..20666392 100644
--- a/auth_volumes/oathkeeper/oathkeeper.yml
+++ b/auth_volumes/oathkeeper/oathkeeper.yml
@@ -96,10 +96,8 @@ mutators:
jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json
claims: |
{
- {{ if eq .Extra.identity.schema_id "staff" }}
{{ if .MatchContext.Header.Get "x-impersonate" }}
"impersonate": {{ .MatchContext.Header.Get "x-impersonate" | toJson }},
{{ end }}
- {{ end }}
"session": {{ .Extra | toJson }}
}
From bd181c97e0db2af06313a963bac855fbaa390fa2 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Sat, 4 Jan 2025 00:03:28 +0000
Subject: [PATCH 05/10] Add comment
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
src/http_app/routes/user_registered_hook.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/http_app/routes/user_registered_hook.py b/src/http_app/routes/user_registered_hook.py
index 16f32f2a..99624f3f 100644
--- a/src/http_app/routes/user_registered_hook.py
+++ b/src/http_app/routes/user_registered_hook.py
@@ -7,5 +7,6 @@
@router.post("/")
async def user_registered(request: Request):
+ # Here we could check the email and add staff metadata to the identity
logging.info("User registered", extra={"body": await request.json()})
return {"user_registered": "OK"}
From e1d4919f23478434d33bf3add50a4b112052abd0 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Sat, 4 Jan 2025 00:06:58 +0000
Subject: [PATCH 06/10] Disable coverage on test endpoint
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
src/http_app/routes/user_registered_hook.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/http_app/routes/user_registered_hook.py b/src/http_app/routes/user_registered_hook.py
index 99624f3f..7914599e 100644
--- a/src/http_app/routes/user_registered_hook.py
+++ b/src/http_app/routes/user_registered_hook.py
@@ -6,7 +6,7 @@
@router.post("/")
-async def user_registered(request: Request):
+async def user_registered(request: Request): # pragma: no cover
# Here we could check the email and add staff metadata to the identity
logging.info("User registered", extra={"body": await request.json()})
return {"user_registered": "OK"}
From 2af831fe685da8e87a0041dde9c3f061d03440be Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Sat, 4 Jan 2025 09:53:26 +0000
Subject: [PATCH 07/10] Proxy all API endpoints to the dev container
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/oathkeeper/access-rules.yml | 5 +++--
docs/zero_trust.md | 2 +-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/auth_volumes/oathkeeper/access-rules.yml b/auth_volumes/oathkeeper/access-rules.yml
index 34748da7..049b7155 100644
--- a/auth_volumes/oathkeeper/access-rules.yml
+++ b/auth_volumes/oathkeeper/access-rules.yml
@@ -73,13 +73,14 @@
config:
to: http://127.0.0.1:8080/login
-# Dev container access to protected /hello endpoint
+# Dev container access to protected /api/* endpoints, to the dev container
- id: "http_app:protected"
upstream:
preserve_host: true
url: "http://dev:8000"
+ strip_path: /api
match:
- url: "http://127.0.0.1:8080/hello<{,/,/**}>"
+ url: "http://127.0.0.1:8080/<{,api/,api/**,openapi.json}>"
methods:
- GET
authenticators:
diff --git a/docs/zero_trust.md b/docs/zero_trust.md
index f7decaa9..ad57a1d8 100644
--- a/docs/zero_trust.md
+++ b/docs/zero_trust.md
@@ -50,7 +50,7 @@ subgraph dn["Internal Docker Network (intranet)"]
OO-->|"Proxies /auth/login, /auth/registration, /dashboard, ... to"|SA
SA-->|Talks to|OK
OO-->|Validates auth sessions using|OK
- OO-->|"Proxies /hello to"|DEV
+ OO-->|"Proxies /api/* requests (authenticated only)"|DEV
OK[Ory Kratos]
OO["Reverse Proxy (Ory Oathkeeper)"]
SA["SecureApp (Ory Kratos SelfService UI Node Example)"]
From 5b106e0401a32c06b796c7850e9f6ed788216e6e Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Mon, 6 Jan 2025 12:50:42 +0000
Subject: [PATCH 08/10] Fix double oathkeeper rule on /
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/oathkeeper/access-rules.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/auth_volumes/oathkeeper/access-rules.yml b/auth_volumes/oathkeeper/access-rules.yml
index 049b7155..b5f4ef39 100644
--- a/auth_volumes/oathkeeper/access-rules.yml
+++ b/auth_volumes/oathkeeper/access-rules.yml
@@ -80,7 +80,7 @@
url: "http://dev:8000"
strip_path: /api
match:
- url: "http://127.0.0.1:8080/<{,api/,api/**,openapi.json}>"
+ url: "http://127.0.0.1:8080/<{api/,api/**,openapi.json}>"
methods:
- GET
authenticators:
From 8901b0fc5fdf84ca37255922c1ca268766a84e77 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Mon, 6 Jan 2025 12:52:07 +0000
Subject: [PATCH 09/10] Make the registration webhook be a blocker for
undesired emails
Signed-off-by: Federico Busetti <729029+febus982@users.noreply.github.com>
---
auth_volumes/kratos/kratos.yml | 16 ++----
auth_volumes/kratos/user_registered.jsonnet | 3 -
src/http_app/routes/user_registered_hook.py | 62 +++++++++++++++++++--
3 files changed, 61 insertions(+), 20 deletions(-)
diff --git a/auth_volumes/kratos/kratos.yml b/auth_volumes/kratos/kratos.yml
index 387e49e9..61099bc6 100644
--- a/auth_volumes/kratos/kratos.yml
+++ b/auth_volumes/kratos/kratos.yml
@@ -81,26 +81,18 @@ selfservice:
registration:
lifespan: 10m
ui_url: http://127.0.0.1:8080/registration
+
after:
password:
hooks:
- - hook: session
- hook: web_hook
config:
url: http://dev:8000/user_registered/
method: "POST"
-# headers: { }
- # https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating
body: file:///etc/config/kratos/user_registered.jsonnet
- can_interrupt: false
- emit_analytics_event: false
-# auth:
-# type: api_key
-# config:
-# name: ""
-# value: ""
-# in: header
-# - hook: show_verification_ui
+ can_interrupt: true
+ emit_analytics_event: true
+ - hook: session
log:
level: info
diff --git a/auth_volumes/kratos/user_registered.jsonnet b/auth_volumes/kratos/user_registered.jsonnet
index 461429fa..8751729f 100644
--- a/auth_volumes/kratos/user_registered.jsonnet
+++ b/auth_volumes/kratos/user_registered.jsonnet
@@ -1,7 +1,4 @@
function(ctx) {
user_id: ctx.identity.id,
- traits: {
email: ctx.identity.traits.email,
- name: ctx.identity.traits.name,
- }
}
diff --git a/src/http_app/routes/user_registered_hook.py b/src/http_app/routes/user_registered_hook.py
index 7914599e..3299918e 100644
--- a/src/http_app/routes/user_registered_hook.py
+++ b/src/http_app/routes/user_registered_hook.py
@@ -1,12 +1,64 @@
import logging
-from fastapi import APIRouter, Request
+from fastapi import APIRouter, status
+from fastapi.responses import JSONResponse, Response
+from pydantic import BaseModel
router = APIRouter(prefix="/user_registered")
+class UserRegisteredWebhook(BaseModel):
+ user_id: str
+ email: str
+
+
@router.post("/")
-async def user_registered(request: Request): # pragma: no cover
- # Here we could check the email and add staff metadata to the identity
- logging.info("User registered", extra={"body": await request.json()})
- return {"user_registered": "OK"}
+async def user_registered(user: UserRegisteredWebhook): # pragma: no cover
+ """
+ Handles the user registration webhook.
+
+ This function is triggered when a user registration webhook is received.
+ It logs the event details, evaluates the email validity, and returns an
+ appropriate HTTP response based on the validation. If the user's email
+ is invalid, it returns an error response along with a structured error
+ message. Otherwise, it confirms successful processing with no additional
+ content.
+
+ Args:
+ user (UserRegisteredWebhook): The webhook payload received when a user
+ registers, containing user details such as email and traits.
+
+ Returns:
+ Response: An HTTP response with a 403 Forbidden status and structured
+ error message if the user email is invalid.
+ Otherwise, an HTTP 204 No Content response to confirm successful
+ processing.
+ """
+ logging.info("User registered", extra={"user": user.model_dump()})
+
+ error_message = {
+ "messages": [
+ {
+ "instance_ptr": "#/traits/email",
+ "messages": [
+ {
+ "id": 123, # Error id to be evaluated in frontend
+ "text": "You are not allowed to register.",
+ "type": "error",
+ "context": { # Additional context we can send to the Frontend
+ "value": "short value",
+ "any": "additional information",
+ },
+ }
+ ],
+ }
+ ]
+ }
+
+ if user.email == "invalid@test.com":
+ return JSONResponse(
+ error_message,
+ status.HTTP_403_FORBIDDEN,
+ )
+ else:
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
From 7f92cb5e793f9f461abee78ab5806a8cb5d95381 Mon Sep 17 00:00:00 2001
From: Federico Busetti <729029+febus982@users.noreply.github.com>
Date: Tue, 7 Jan 2025 10:43:42 +0000
Subject: [PATCH 10/10] PyCharm profiles
---
.idea/.gitignore | 8 -------
.idea/bootstrap-python-fastapi.iml | 13 ++++++------
.idea/inspectionProfiles/Project_Default.xml | 21 -------------------
.../inspectionProfiles/profiles_settings.xml | 6 ++++++
.idea/misc.xml | 11 ++--------
.idea/runConfigurations/Tests.xml | 2 +-
.idea/vcs.xml | 2 +-
7 files changed, 17 insertions(+), 46 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b81..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/bootstrap-python-fastapi.iml b/.idea/bootstrap-python-fastapi.iml
index 4e1b42f2..6a0f2b0a 100644
--- a/.idea/bootstrap-python-fastapi.iml
+++ b/.idea/bootstrap-python-fastapi.iml
@@ -4,15 +4,16 @@
+
-
+
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 0c3737ba..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index bb0acce9..a72c6a13 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,14 +1,7 @@
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Tests.xml b/.idea/runConfigurations/Tests.xml
index a41a4bbc..92e69331 100644
--- a/.idea/runConfigurations/Tests.xml
+++ b/.idea/runConfigurations/Tests.xml
@@ -14,7 +14,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7f..35eb1ddf 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file