feat(sdk): users::email_available for anonymous registration pre-check (S1)

`users::find_by_email` requires an authenticated principal (F-S-003,
anti-enumeration), so the natural check-then-create register pattern 502s
for anonymous public scripts. Add `users::email_available(email) -> bool`,
gated like `create` (the registration write path) but without the
anonymous rejection: it leaks only a single boolean — no more than
`create`'s own uniqueness error already does — so self-serve register
scripts can pre-check. Also document find_by_email's principal
requirement in the SDK doc-comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-12 20:43:18 +02:00
parent ae2134e62d
commit 7ffbb8de87
4 changed files with 64 additions and 1 deletions

View File

@@ -200,6 +200,15 @@ pub trait UsersService: Send + Sync {
email: &str,
) -> Result<Option<AppUser>, UsersError>;
/// Anonymous-safe existence probe for self-serve registration.
/// Returns `true` when the email is free to register, `false` when
/// it's taken. Unlike [`find_by_email`](Self::find_by_email) (which
/// requires an authenticated principal to prevent user enumeration),
/// this leaks only a single boolean — no id, profile, or timing
/// distinction beyond "exists / doesn't" — so a public registration
/// script can pre-check before calling `create`.
async fn email_available(&self, cx: &SdkCallCx, email: &str) -> Result<bool, UsersError>;
async fn update(
&self,
cx: &SdkCallCx,
@@ -387,6 +396,9 @@ impl UsersService for NoopUsersService {
) -> Result<Option<AppUser>, UsersError> {
Err(UsersError::Backend(NOOP_MSG.into()))
}
async fn email_available(&self, _cx: &SdkCallCx, _email: &str) -> Result<bool, UsersError> {
Err(UsersError::Backend(NOOP_MSG.into()))
}
async fn update(
&self,
_cx: &SdkCallCx,