Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fast-jelly! the Gel auth extension bindings for FastAPI #3

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

fantix
Copy link
Member

@fantix fantix commented Feb 26, 2025

This adds the binding of Gel auth extension for FastAPI, with example application:

10 0 2 14_8000_docs

Depends on geldata/gel-python#586

  • Add blocking binding
  • More meaningful APIs?
  • Add tests

scotttrinh and others added 4 commits February 26, 2025 11:20
Comment on lines 35 to 45
sign_up_response: Annotated[
core_email_password.SignUpResponse, Depends(email_password.handle_sign_up)
],
):
if not isinstance(sign_up_response, core_email_password.SignUpFailedResponse):
user = await create_user_qry.create_user(
client,
name=email,
identity_id=sign_up_response.identity_id,
)
print(f"Created user: {json.dumps(user, default=str)}")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I already mentioned with @1st1 - the creation of the identity (extension domain) and user (application domain) are not done in a single transaction because they are applied each under HTTP and binary protocol. If the identity is created while the user isn't, would the example application be able to recover?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would the example application be able to recover?

Not automatically, but I suppose you could write a query to find all "orphan" identities and try to create the relevant user. FWIW, there is nothing that says users have to be created after the Identity either: if you're implementing an invite flow, for instance, you might already have a User in which case you'll be linking to an existing user.

But, sure, this is definitely a classic distributed systems problem, no doubt about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, if we use EdgeQL - if possible - for all of the work done here, it won't be a problem for at least this particular use case (quite a common one, I think?).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of the work done here

"All of the work" as in creating the ext::auth::Identity? I think that goes against the design of the auth extension which is that it does everything for you via the HTTP API and gives you hooks into the lifecycle for you to do whatever kind of application-level modeling related to that you want to do. Or maybe I'm misunderstanding the suggestion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"All of the work" as in creating the ext::auth::Identity?

Exactly.

I just verified: if create_user() fails, as a user, I have no way to sign in, even if I received the verification email and activated my account. In production, the developer has to "write a query to find all "orphan" identities and try to create the relevant user", or alternatlvely, verify and create the user on verification/signin/etc. I think we should also do this in this example, as developers may just fork this example into their production systems.

that goes against the design of the auth extension

Yeah, I know, that was exactly my question. ;) but now that we're here, let's just make the amendment and documentation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's just make the amendment and documentation.

What is "the amendment and documentation" that you're suggesting? Do you mean we should write some guidance on what to do if your application is unable to create the matching User object for a given ext::auth::Identity? Like a kind of troubleshooting guide? That seems like something we can add to the documentation, sure! Is there something actionable about that in this PR?

I'm still a little confused about the suggestion that we somehow try to create the ext::auth::Identity here in the callback: that just won't work since all of the other side effects of the auth extension in the Gel server require that an identity is created and cannot be delayed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a little confused about the suggestion

Oh sorry I meant something completely different than the current HTTP-based design, where you would do something like this in EdgeQL:

with identity := ext::auth::sign_up()
insert User { ..., identities := identity }

But this is in another parallel universe and comes with its own issues (like where to put the password hashing job, etc.).

Do you mean we should write some guidance

Yeah, I'd say something like this in the documentation:

Accessing the Auth extension APIs through HTTP means, such database
accesses cannot be a part of your transaction; handling the non-atomicity
here is your responsibility. In other words, your application should be ready
to handle the cases when e.g. the HTTP call to `/auth/register` succeeded
but the EdgeQL to create a corresponding `User` (from your own schema) failed.

And for this example application here, I'll try to add some comments as well as the extra handling for the missing user object.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is in another parallel universe

Ahh, yeah I get it now 👍 Yeah, I think even in that universe there is so much HTTP in the mix for things like OAuth that it's frankly not worth it, but the email password flow is close to being implementable completely in EdgeQL 😂

Yeah, I'd say something like this in the documentation:

Yeah, great suggestion. I think it's heavily dependent on your in-app authentication data model, but we can provide some good examples both in our guides and in example apps which try to handle these complications. I haven't seen many other Auth products put a lot of guidance into these synchronization issues, so this could be a nice stand-out for us. To be fair, most other auth products own the User model too, but they all will suffer from this if they have anything tying app data to auth data.

@fantix fantix requested review from 1st1, elprans and scotttrinh March 6, 2025 20:43
):
sign_up_response = await auth.handle_sign_up(
sign_up_body,
verify_url=str(request.url_for("verify")),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generates the public URL for the "verify" endpoint at runtime.

) -> ext.EmailPassword:
return ext.EmailPassword(
await core.make_async(client),
secure_cookie=request.base_url.is_secure,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This detects if the app is visited under TLS or not - it's convenient if the cookie follows the security setup for development, but this should be enforced on production environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants