|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from pydantic import BaseModel, EmailStr, Field |
| 4 | + |
| 5 | +from fastapi_cloudflow import Arg, AssignStep, Context, HttpStep, step, workflow |
| 6 | + |
| 7 | + |
| 8 | +class SignupRequest(BaseModel): |
| 9 | + email: EmailStr |
| 10 | + password: str = Field(min_length=8) |
| 11 | + |
| 12 | + |
| 13 | +class UserDraft(BaseModel): |
| 14 | + email: EmailStr |
| 15 | + hashed_password: str |
| 16 | + |
| 17 | + |
| 18 | +class IdentityReq(BaseModel): |
| 19 | + email: EmailStr |
| 20 | + secret: str |
| 21 | + |
| 22 | + |
| 23 | +class IdentityRes(BaseModel): |
| 24 | + external_id: str |
| 25 | + ok: bool |
| 26 | + |
| 27 | + |
| 28 | +class UserCreated(BaseModel): |
| 29 | + user_id: str |
| 30 | + email: EmailStr |
| 31 | + |
| 32 | + |
| 33 | +@step(name="hash-password") |
| 34 | +async def hash_password(ctx: Context, data: SignupRequest) -> UserDraft: |
| 35 | + return UserDraft(email=data.email, hashed_password=f"hashed:{data.password}") |
| 36 | + |
| 37 | + |
| 38 | +adapt_to_idp = AssignStep( |
| 39 | + "draft->idp", |
| 40 | + UserDraft, |
| 41 | + IdentityReq, |
| 42 | + expr={ |
| 43 | + "email": "${payload.email}", |
| 44 | + "secret": "${payload.hashed_password}", |
| 45 | + }, |
| 46 | +) |
| 47 | + |
| 48 | + |
| 49 | +idp_call = HttpStep( |
| 50 | + name="idp-call", |
| 51 | + input_model=IdentityReq, |
| 52 | + output_model=IdentityRes, |
| 53 | + method="POST", |
| 54 | + url=Arg.env("IDP_URL") / "signup", |
| 55 | + auth={"type": "OIDC", "audience": Arg.env("IDP_URL")}, |
| 56 | +) |
| 57 | + |
| 58 | + |
| 59 | +@step(name="persist-user") |
| 60 | +async def persist_user(ctx: Context, data: IdentityRes) -> UserCreated: |
| 61 | + return UserCreated(user_id=data.external_id, email="user@example.com") |
| 62 | + |
| 63 | + |
| 64 | +USER_SIGNUP = (workflow("user-signup") >> hash_password >> adapt_to_idp >> idp_call >> persist_user).build() |
0 commit comments