ferrox-py-auth 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ # init
@@ -0,0 +1,52 @@
1
+ from fastapi import Request
2
+ from pydantic import BaseModel
3
+ from ferrox_py.core.controllers import BaseController
4
+ from ferrox_py.core.container import Container
5
+ from ..services.auth_service import AuthService
6
+ from ..services.gdpr_service import GdprService
7
+ from ..security.rbac import require_roles
8
+
9
+ class LoginPayload(BaseModel):
10
+ email: str
11
+ password_hash: str
12
+
13
+ class SsoPayload(BaseModel):
14
+ provider: str
15
+ provider_id: str
16
+ email: str
17
+
18
+ class AuthController(BaseController):
19
+ def __init__(self, container: Container):
20
+ super().__init__(prefix="/auth", tags=["Auth & IAM"])
21
+
22
+ self.auth = AuthService(container.resolve("JwtService"))
23
+ self.gdpr = GdprService(self.auth)
24
+
25
+ @self.router.get("/config")
26
+ async def get_config():
27
+ return self.ok(self.auth.get_config(), "Auth settings retrieved")
28
+
29
+ @self.router.post("/register")
30
+ async def register(payload: LoginPayload):
31
+ user = await self.auth.register_local(payload.email, payload.password_hash)
32
+ return self.created(user.model_dump(), "User registered. Please check your email.")
33
+
34
+ @self.router.post("/sso")
35
+ async def sso_login(payload: SsoPayload):
36
+ token = await self.auth.login_sso(payload.provider, payload.provider_id, payload.email)
37
+ return self.ok({"token": token}, "SSO Login successful")
38
+
39
+ @self.router.get("/gdpr/export")
40
+ @require_roles("user", "admin")
41
+ async def export_my_data(request: Request):
42
+ # Extract user_id from the authenticated request state
43
+ user_id = request.state.user["sub"]
44
+ data = await self.gdpr.export_data(user_id)
45
+ return self.ok(data, "GDPR Export ready")
46
+
47
+ @self.router.delete("/gdpr/forget")
48
+ @require_roles("user", "admin")
49
+ async def delete_my_account(request: Request):
50
+ user_id = request.state.user["sub"]
51
+ success = await self.gdpr.forget_me(user_id)
52
+ return self.ok({"deleted": success}, "Account permanently deleted")
@@ -0,0 +1,16 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Optional
3
+ from datetime import datetime
4
+
5
+ class Identity(BaseModel):
6
+ provider: str # e.g., 'local', 'google', 'facebook', 'apple'
7
+ provider_id: str # ID from the external provider or hash of password
8
+ created_at: datetime = Field(default_factory=datetime.utcnow)
9
+
10
+ class User(BaseModel):
11
+ id: str
12
+ email: str
13
+ email_verified: bool = False
14
+ roles: List[str] = ["user"]
15
+ identities: List[Identity] = []
16
+ created_at: datetime = Field(default_factory=datetime.utcnow)
@@ -0,0 +1,26 @@
1
+ from functools import wraps
2
+ from typing import List
3
+ from fastapi import Request
4
+ from ferrox_py.core.errors import FerroxError
5
+
6
+ def require_roles(*roles: str):
7
+ """
8
+ RBAC Decorator.
9
+ Extracts the user 'roles' claim from the JWT (attached to request.state.user)
10
+ and validates it against the required roles.
11
+ """
12
+ def decorator(func):
13
+ @wraps(func)
14
+ async def wrapper(request: Request, *args, **kwargs):
15
+ user_data = getattr(request.state, "user", None)
16
+ if not user_data:
17
+ raise FerroxError("Unauthorized - No JWT token found", 401)
18
+
19
+ user_roles = user_data.get("roles", [])
20
+
21
+ if not any(role in user_roles for role in roles):
22
+ raise FerroxError(f"Forbidden - Requires one of roles: {roles}", 403)
23
+
24
+ return await func(request, *args, **kwargs)
25
+ return wrapper
26
+ return decorator
@@ -0,0 +1,52 @@
1
+ from ferrox_py.core.provider import injectable
2
+ from ferrox_py.core.errors import FerroxError
3
+ from ferrox_py.security.jwt import JwtService
4
+ from ..models.user import User, Identity
5
+ from typing import Dict, Any
6
+
7
+ @injectable()
8
+ class AuthService:
9
+ def __init__(self, jwt_service: JwtService):
10
+ self.jwt = jwt_service
11
+ self.enforce_sso_only = False # Feature flag configurable by the application
12
+
13
+ # Mock DB
14
+ self._users_db: Dict[str, User] = {}
15
+
16
+ def get_config(self) -> dict:
17
+ """Returns auth settings for the frontend (e.g. to hide password fields)."""
18
+ return {"enforce_sso_only": self.enforce_sso_only}
19
+
20
+ async def register_local(self, email: str, password_hash: str) -> User:
21
+ if self.enforce_sso_only:
22
+ raise FerroxError("Local registration is disabled by SSO policy", 403)
23
+
24
+ if email in self._users_db:
25
+ raise FerroxError("Email already in use", 400)
26
+
27
+ user = User(
28
+ id=email,
29
+ email=email,
30
+ identities=[Identity(provider="local", provider_id=password_hash)]
31
+ )
32
+ self._users_db[email] = user
33
+
34
+ # Here we would trigger the MailerService for email_verified=True
35
+ print(f"AuthService: Emitting Email Confirmation for {email}")
36
+ return user
37
+
38
+ async def login_sso(self, provider: str, provider_id: str, email: str) -> str:
39
+ """Handles SSO login or account linkage if user already exists."""
40
+ user = self._users_db.get(email)
41
+
42
+ if not user:
43
+ # Create new user, auto-verify email since it comes from trusted SSO
44
+ user = User(id=email, email=email, email_verified=True, identities=[])
45
+ self._users_db[email] = user
46
+
47
+ # Link identity if not present
48
+ if not any(i.provider == provider for i in user.identities):
49
+ user.identities.append(Identity(provider=provider, provider_id=provider_id))
50
+
51
+ # Issue JWT
52
+ return self.jwt.sign({"sub": user.id, "roles": user.roles})
@@ -0,0 +1,31 @@
1
+ from ferrox_py.core.provider import injectable
2
+ from ferrox_py.core.errors import FerroxError
3
+ from .auth_service import AuthService
4
+ from typing import Dict
5
+
6
+ @injectable()
7
+ class GdprService:
8
+ def __init__(self, auth_service: AuthService):
9
+ self.auth = auth_service
10
+
11
+ async def export_data(self, user_id: str) -> Dict:
12
+ """Returns all PII (Personally Identifiable Information) for GDPR export."""
13
+ user = self.auth._users_db.get(user_id)
14
+ if not user:
15
+ raise FerroxError("User not found", 404)
16
+
17
+ return {
18
+ "account": user.model_dump(),
19
+ "consent_logs": [],
20
+ "activity_logs": []
21
+ }
22
+
23
+ async def forget_me(self, user_id: str) -> bool:
24
+ """Right to be forgotten: Hard deletes the user and all associated identities."""
25
+ if user_id in self.auth._users_db:
26
+ del self.auth._users_db[user_id]
27
+ # In a real scenario, this would emit an event to soft-delete or anonymize
28
+ # related records across the entire microservice ecosystem.
29
+ print(f"GdprService: User {user_id} completely deleted from system.")
30
+ return True
31
+ return False
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.5
2
+ Name: ferrox-py-auth
3
+ Version: 1.0.0
4
+ Summary: IAM, SSO, GDPR and RBAC suite for Ferrox ecosystem.
5
+ Author: AI-Autistic-Intelligence
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: ferrox-py>=1.0.0
8
+ Requires-Dist: pydantic>=2.0
9
+ Description-Content-Type: text/markdown
10
+
11
+ # Ferrox-Py-Auth
12
+
13
+ ## 1. Overview (What does this do?)
14
+ The `ferrox-py-auth` module is a specialized extension for the Ferrox ecosystem that provides a comprehensive Identity & Access Management (IAM) suite. It delivers pre-built functionalities for managing user registrations, Single Sign-On (SSO), Role-Based Access Control (RBAC), and compliance with European data privacy laws (GDPR).
15
+
16
+ ## 2. Philosophy (Why does it exist?)
17
+ Authentication and authorization are complex and highly sensitive domains. Re-implementing secure login flows, password hashing, SSO integration, and GDPR-compliant data deletion mechanisms for every new project is not only inefficient but heavily prone to critical security vulnerabilities. This module exists to provide a battle-tested, standardized "plug-and-play" identity solution that adheres to strict zero-trust principles.
18
+
19
+ ## 3. Target Audience (Who is it for?)
20
+ This module is intended for backend engineers and security architects building Enterprise SaaS, financial platforms, or any consumer-facing application where data privacy (like GDPR compliance) and secure access controls are legal or operational requirements.
21
+
22
+ ## 4. Architecture (How does it work?)
23
+ `ferrox-py-auth` plugs directly into the **7-Layer Onion Request Pipeline** of the core `ferrox-py` framework, specifically occupying Layers 3 (Threat Engine) and 4 (Auth Guards). It registers a set of domain Services (`AuthService`, `GdprService`) into the central IoC Container and exposes abstract Data Models (`User`, `UserIdentity`) that can be persisted via MongoDB or SQLAlchemy adapters provided by the `ferrox-py` Data Component.
24
+
25
+ ## 5. Installation / Setup
26
+ This package requires the core `ferrox-py` framework to function. Install it via pip:
27
+
28
+ ```bash
29
+ pip install ferrox-py-auth
30
+ ```
31
+ Ensure you have configured a persistent database layer (either SQL or NoSQL) in your core application to store the user identities.
32
+
33
+ ## 6. Quickstart (Usage)
34
+ ```python
35
+ from ferrox_py.core.app import FerroxApp
36
+ from ferrox_py.core.container import Container
37
+ from ferrox_py_auth import AuthModule
38
+
39
+ # Initialize the IoC container
40
+ container = Container()
41
+
42
+ # The AuthModule automatically registers the AuthService, GdprService,
43
+ # and Auth Guards into the container and application lifecycle.
44
+ container.register_module(AuthModule)
45
+
46
+ app = FerroxApp(container)
47
+ app.start()
48
+ ```
49
+
50
+ ## 7. Ecosystem Integration
51
+ This module relies heavily on the **Security** component of the core `ferrox-py` framework for generating and parsing PASETO/JWT tokens. It also serves as a foundational dependency for `ferrox-py-commerce`, as a validated User context is required before initiating billing or subscription processes.
@@ -0,0 +1,9 @@
1
+ ferrox_py_auth/__init__.py,sha256=4zALCCxfSyzOKNmB_Y-R1A0iUhPpCOrKbkJ1srJWyxo,7
2
+ ferrox_py_auth/controllers/auth_controller.py,sha256=bI7qU6moF2unIxwGQziwwgDJZM_sxb5yLnJ6nkizhvA,2132
3
+ ferrox_py_auth/models/user.py,sha256=b4Qma_zHFx7mO1RoaYPqaIYRa7QJy6sqL8QLr6TtgUA,554
4
+ ferrox_py_auth/security/rbac.py,sha256=DUUFswQUYhQ18TQHua1ltUPuGnpRgTXMxlZPqfMH-I8,948
5
+ ferrox_py_auth/services/auth_service.py,sha256=L7ajfKLjOw6EaavYOulcxNwPi56HntQt3zhEhULUvwY,2091
6
+ ferrox_py_auth/services/gdpr_service.py,sha256=sr2k5Y5HH8T88CI9FZOQmD6pZIJOSGgPgPS3-qzmxCQ,1201
7
+ ferrox_py_auth-1.0.0.dist-info/METADATA,sha256=clnWySXRPSWZpKQemQLR3Ddbkc5luU4Q1SoyY2Ze0xI,2968
8
+ ferrox_py_auth-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
9
+ ferrox_py_auth-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any