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

Only show the password change section if the user has a password #4100

Merged
merged 2 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions crates/handlers/src/graphql/model/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,16 @@ impl User {
)
.await
}

/// Check if the user has a password set.
async fn has_password(&self, ctx: &Context<'_>) -> Result<bool, async_graphql::Error> {
let state = ctx.state();
let mut repo = state.repository().await?;

let password = repo.user_password().active(&self.0).await?;

Ok(password.is_some())
}
}

/// A session in an application, either a compatibility or an OAuth 2.0 one
Expand Down
4 changes: 4 additions & 0 deletions frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2059,6 +2059,10 @@ type User implements Node {
"""
last: Int
): AppSessionConnection!
"""
Check if the user has a password set.
"""
hasPassword: Boolean!
}

"""
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Documents = {
"\n query UserEmailList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": typeof types.UserEmailListDocument,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": typeof types.UserEmailList_SiteConfigFragmentDoc,
"\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": typeof types.BrowserSessionsOverview_UserFragmentDoc,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": typeof types.BrowserSessionListDocument,
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": typeof types.SessionsOverviewDocument,
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": typeof types.AppSessionsListDocument,
Expand Down Expand Up @@ -91,7 +91,7 @@ const documents: Documents = {
"\n query UserEmailList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": types.UserEmailListDocument,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": types.UserEmailList_SiteConfigFragmentDoc,
"\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": types.BrowserSessionsOverview_UserFragmentDoc,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument,
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewDocument,
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": types.AppSessionsListDocument,
Expand Down Expand Up @@ -224,7 +224,7 @@ export function graphql(source: "\n fragment BrowserSessionsOverview_user on Us
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
export function graphql(source: "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,8 @@ export type User = Node & {
createdAt: Scalars['DateTime']['output'];
/** Get the list of emails, chronologically sorted */
emails: UserEmailConnection;
/** Check if the user has a password set. */
hasPassword: Scalars['Boolean']['output'];
/** ID of the object. */
id: Scalars['ID']['output'];
/** When the user was locked out. */
Expand Down Expand Up @@ -1687,7 +1689,7 @@ export type BrowserSessionsOverview_UserFragment = { __typename?: 'User', id: st
export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>;


export type UserProfileQuery = { __typename?: 'Query', viewerSession: { __typename: 'Anonymous' } | { __typename: 'BrowserSession', id: string, user: { __typename?: 'User', emails: { __typename?: 'UserEmailConnection', totalCount: number } } } | { __typename: 'Oauth2Session' }, siteConfig: (
export type UserProfileQuery = { __typename?: 'Query', viewerSession: { __typename: 'Anonymous' } | { __typename: 'BrowserSession', id: string, user: { __typename?: 'User', hasPassword: boolean, emails: { __typename?: 'UserEmailConnection', totalCount: number } } } | { __typename: 'Oauth2Session' }, siteConfig: (
{ __typename?: 'SiteConfig', emailChangeAllowed: boolean, passwordLoginEnabled: boolean }
& { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment;'PasswordChange_SiteConfigFragment': PasswordChange_SiteConfigFragment } }
) };
Expand Down Expand Up @@ -2302,6 +2304,7 @@ export const UserProfileDocument = new TypedDocumentString(`
... on BrowserSession {
id
user {
hasPassword
emails(first: 0) {
totalCount
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/_account.index.lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function Index(): React.ReactElement {
</>
)}

{siteConfig.passwordLoginEnabled && (
{siteConfig.passwordLoginEnabled && viewerSession.user.hasPassword && (
<>
<Collapsible.Section
defaultOpen
Expand Down
1 change: 1 addition & 0 deletions frontend/src/routes/_account.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const QUERY = graphql(/* GraphQL */ `
... on BrowserSession {
id
user {
hasPassword
emails(first: 0) {
totalCount
}
Expand Down
10 changes: 10 additions & 0 deletions frontend/stories/routes/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ const userProfileHandler = ({
passwordLoginEnabled,
passwordChangeAllowed,
emailTotalCount,
hasPassword,
}: {
emailChangeAllowed: boolean;
passwordLoginEnabled: boolean;
passwordChangeAllowed: boolean;
emailTotalCount: number;
hasPassword: boolean;
}): GraphQLHandler =>
mockUserProfileQuery(() =>
HttpResponse.json({
Expand All @@ -47,6 +49,7 @@ const userProfileHandler = ({
__typename: "BrowserSession",
id: "session-id",
user: {
hasPassword,
emails: {
totalCount: emailTotalCount,
},
Expand Down Expand Up @@ -130,6 +133,7 @@ export const MultipleEmails: Story = {
passwordChangeAllowed: true,
emailChangeAllowed: true,
emailTotalCount: 3,
hasPassword: true,
}),
threeEmailsHandler,
],
Expand All @@ -147,6 +151,7 @@ export const NoEmails: Story = {
passwordChangeAllowed: true,
emailChangeAllowed: false,
emailTotalCount: 0,
hasPassword: true,
}),
],
},
Expand All @@ -163,6 +168,7 @@ export const MultipleEmailsNoChange: Story = {
passwordChangeAllowed: true,
emailChangeAllowed: false,
emailTotalCount: 3,
hasPassword: true,
}),
threeEmailsHandler,
],
Expand All @@ -180,6 +186,7 @@ export const NoEmailChange: Story = {
passwordChangeAllowed: true,
emailChangeAllowed: false,
emailTotalCount: 1,
hasPassword: true,
}),
],
},
Expand All @@ -196,6 +203,7 @@ export const NoPasswordChange: Story = {
passwordChangeAllowed: false,
emailChangeAllowed: true,
emailTotalCount: 1,
hasPassword: true,
}),
],
},
Expand All @@ -212,6 +220,7 @@ export const NoPasswordLogin: Story = {
passwordChangeAllowed: false,
emailChangeAllowed: true,
emailTotalCount: 1,
hasPassword: true,
}),
],
},
Expand All @@ -228,6 +237,7 @@ export const NoPasswordNoEmailChange: Story = {
passwordChangeAllowed: false,
emailChangeAllowed: false,
emailTotalCount: 0,
hasPassword: false,
}),
],
},
Expand Down
1 change: 1 addition & 0 deletions frontend/tests/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const handlers = [
__typename: "BrowserSession",
id: "browser-session-id",
user: {
hasPassword: true,
emails: {
totalCount: 1,
},
Expand Down
Loading