diff --git a/crates/handlers/src/graphql/query/user.rs b/crates/handlers/src/graphql/query/user.rs index adf8b75dc..98d575560 100644 --- a/crates/handlers/src/graphql/query/user.rs +++ b/crates/handlers/src/graphql/query/user.rs @@ -28,6 +28,18 @@ enum UsernameUnavailableReason { Invalid, } +/// Public information about a registration token, returned by anonymous lookup. +/// Does not expose admin details like usage counts or expiration. +#[derive(SimpleObject)] +struct RegistrationTokenInfo { + /// Whether the token is currently valid for registration. + valid: bool, + /// A username imposed by this token, if any. + username: Option, + /// An email imposed by this token, if any. + email: Option, +} + /// The result of a username availability check. #[derive(SimpleObject)] struct UsernameAvailability { @@ -168,6 +180,35 @@ impl UserQuery { }) } + /// Look up a registration token by its string value. + /// + /// Returns public information about the token (validity, imposed + /// username/email). Returns `null` if the token does not exist. + /// + /// This query is accessible to anonymous users, as it is used during + /// the registration flow. + async fn registration_token( + &self, + ctx: &Context<'_>, + token: String, + ) -> Result, async_graphql::Error> { + let state = ctx.state(); + let mut repo = state.repository().await?; + + let registration_token = repo.user_registration_token().find_by_token(&token).await?; + repo.cancel().await?; + + let Some(registration_token) = registration_token else { + return Ok(None); + }; + + Ok(Some(RegistrationTokenInfo { + valid: registration_token.is_valid(state.clock().now()), + username: registration_token.username, + email: registration_token.email, + })) + } + /// Fetch a user by its username. async fn user_by_username( &self, diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 9aca097e2..7a16b0157 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -1194,6 +1194,16 @@ type Query { """ usernameAvailable(username: String!): UsernameAvailability! """ + Look up a registration token by its string value. + + Returns public information about the token (validity, imposed + username/email). Returns `null` if the token does not exist. + + This query is accessible to anonymous users, as it is used during + the registration flow. + """ + registrationToken(token: String!): RegistrationTokenInfo + """ Fetch a user by its username. """ userByUsername(username: String!): User @@ -1271,6 +1281,25 @@ type Query { viewerSession: ViewerSession! } +""" +Public information about a registration token, returned by anonymous lookup. +Does not expose admin details like usage counts or expiration. +""" +type RegistrationTokenInfo { + """ + Whether the token is currently valid for registration. + """ + valid: Boolean! + """ + A username imposed by this token, if any. + """ + username: String + """ + An email imposed by this token, if any. + """ + email: String +} + """ The input for the `removeEmail` mutation """