mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-05-24 21:45:31 +00:00
Pipe session_limit to frontend
This commit is contained in:
@@ -59,6 +59,9 @@ pub struct SiteConfig {
|
||||
|
||||
/// Experimental plan management iframe URI.
|
||||
plan_management_iframe_uri: Option<String>,
|
||||
|
||||
/// Limits on the number of application sessions that each user can have
|
||||
session_limit: Option<SessionLimitConfig>,
|
||||
}
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
@@ -79,6 +82,25 @@ pub enum CaptchaService {
|
||||
HCaptcha,
|
||||
}
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
pub struct SessionLimitConfig {
|
||||
pub soft_limit: u64,
|
||||
pub hard_limit: u64,
|
||||
pub hard_limit_eviction: bool,
|
||||
}
|
||||
|
||||
impl SessionLimitConfig {
|
||||
/// Create a new [`SessionLimitConfig`] from the data model
|
||||
/// [`mas_data_model::SessionLimitConfig`].
|
||||
pub fn new(data_model: &mas_data_model::SessionLimitConfig) -> Self {
|
||||
Self {
|
||||
soft_limit: data_model.soft_limit.get(),
|
||||
hard_limit: data_model.hard_limit.get(),
|
||||
hard_limit_eviction: data_model.hard_limit_eviction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[ComplexObject]
|
||||
impl SiteConfig {
|
||||
/// The ID of the site configuration.
|
||||
@@ -106,6 +128,10 @@ impl SiteConfig {
|
||||
minimum_password_complexity: data_model.minimum_password_complexity,
|
||||
login_with_email_allowed: data_model.login_with_email_allowed,
|
||||
plan_management_iframe_uri: data_model.plan_management_iframe_uri.clone(),
|
||||
session_limit: data_model
|
||||
.session_limit
|
||||
.as_ref()
|
||||
.map(SessionLimitConfig::new),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1402,6 +1402,12 @@ A client session, either compat or OAuth 2.0
|
||||
"""
|
||||
union Session = CompatSession | Oauth2Session
|
||||
|
||||
type SessionLimitConfig {
|
||||
softLimit: Int!
|
||||
hardLimit: Int!
|
||||
hardLimitEviction: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
The state of a session
|
||||
"""
|
||||
@@ -1762,6 +1768,10 @@ type SiteConfig implements Node {
|
||||
"""
|
||||
planManagementIframeUri: String
|
||||
"""
|
||||
Limits on the number of application sessions that each user can have
|
||||
"""
|
||||
sessionLimit: SessionLimitConfig
|
||||
"""
|
||||
The ID of the site configuration.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
@@ -52,7 +52,7 @@ type Documents = {
|
||||
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
|
||||
"\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": typeof types.PlanManagementTabDocument,
|
||||
"\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 # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n": typeof types.SessionsOverviewDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n\n siteConfig {\n sessionLimit {\n softLimit\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,
|
||||
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n planManagementIframeUri\n }\n }\n": typeof types.CurrentUserGreetingDocument,
|
||||
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": typeof types.OAuth2ClientDocument,
|
||||
@@ -109,7 +109,7 @@ const documents: Documents = {
|
||||
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": types.UserProfileDocument,
|
||||
"\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": types.PlanManagementTabDocument,
|
||||
"\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 # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n": types.SessionsOverviewDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n\n siteConfig {\n sessionLimit {\n softLimit\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,
|
||||
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n planManagementIframeUri\n }\n }\n": types.CurrentUserGreetingDocument,
|
||||
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": types.OAuth2ClientDocument,
|
||||
@@ -280,7 +280,7 @@ export function graphql(source: "\n query BrowserSessionList(\n $first: Int\
|
||||
/**
|
||||
* 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 SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n"): typeof import('./graphql').SessionsOverviewDocument;
|
||||
export function graphql(source: "\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n\n siteConfig {\n sessionLimit {\n softLimit\n }\n }\n }\n"): typeof import('./graphql').SessionsOverviewDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1078,6 +1078,13 @@ export type ResendRecoveryEmailStatus =
|
||||
/** A client session, either compat or OAuth 2.0 */
|
||||
export type Session = CompatSession | Oauth2Session;
|
||||
|
||||
export type SessionLimitConfig = {
|
||||
__typename?: 'SessionLimitConfig';
|
||||
hardLimit: Scalars['Int']['output'];
|
||||
hardLimitEviction: Scalars['Boolean']['output'];
|
||||
softLimit: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
/** The state of a session */
|
||||
export type SessionState =
|
||||
/** The session is active. */
|
||||
@@ -1302,6 +1309,8 @@ export type SiteConfig = Node & {
|
||||
policyUri?: Maybe<Scalars['Url']['output']>;
|
||||
/** The server name of the homeserver. */
|
||||
serverName: Scalars['String']['output'];
|
||||
/** Limits on the number of application sessions that each user can have */
|
||||
sessionLimit?: Maybe<SessionLimitConfig>;
|
||||
/** The URL to the terms of service. */
|
||||
tosUri?: Maybe<Scalars['Url']['output']>;
|
||||
};
|
||||
@@ -1899,7 +1908,7 @@ export type SessionsOverviewQuery = { __typename?: 'Query', viewer:
|
||||
{ __typename: 'User', id: string, unfilteredAppSessions: { __typename?: 'AppSessionConnection', totalCount: number } }
|
||||
& { ' $fragmentRefs'?: { 'BrowserSessionsOverview_UserFragment': BrowserSessionsOverview_UserFragment } }
|
||||
)
|
||||
};
|
||||
, siteConfig: { __typename?: 'SiteConfig', sessionLimit?: { __typename?: 'SessionLimitConfig', softLimit: number } | null } };
|
||||
|
||||
export type AppSessionsListQueryVariables = Exact<{
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2710,6 +2719,11 @@ export const SessionsOverviewDocument = new TypedDocumentString(`
|
||||
}
|
||||
}
|
||||
}
|
||||
siteConfig {
|
||||
sessionLimit {
|
||||
softLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment BrowserSessionsOverview_user on User {
|
||||
id
|
||||
@@ -3401,7 +3415,7 @@ export const mockBrowserSessionListQuery = (resolver: GraphQLResponseResolver<Br
|
||||
* mockSessionsOverviewQuery(
|
||||
* ({ query, variables }) => {
|
||||
* return HttpResponse.json({
|
||||
* data: { viewer }
|
||||
* data: { viewer, siteConfig }
|
||||
* })
|
||||
* },
|
||||
* requestOptions
|
||||
|
||||
@@ -43,6 +43,12 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
siteConfig {
|
||||
sessionLimit {
|
||||
softLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -141,9 +147,10 @@ function Sessions(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { inactive, pagination } = Route.useLoaderDeps();
|
||||
const {
|
||||
data: { viewer: overviewViewer },
|
||||
data: { viewer: overviewViewer, siteConfig },
|
||||
} = useSuspenseQuery(query);
|
||||
if (overviewViewer.__typename !== "User") throw notFound();
|
||||
const { sessionLimit } = siteConfig;
|
||||
|
||||
const { data } = useSuspenseQuery(listQuery(pagination, inactive));
|
||||
if (data.viewer.__typename !== "User") throw notFound();
|
||||
@@ -159,16 +166,32 @@ function Sessions(): React.ReactElement {
|
||||
const edges = [...appSessions.edges].reverse();
|
||||
|
||||
// By default, we just show a "X devices" header
|
||||
let deviceHeaderText = t("frontend.user_sessions_overview.num_devices_header", {
|
||||
num_devices: appSessions.totalCount,
|
||||
});
|
||||
let deviceHeaderText = t(
|
||||
"frontend.user_sessions_overview.num_devices_header",
|
||||
{
|
||||
num_devices: appSessions.totalCount,
|
||||
},
|
||||
);
|
||||
// But if we're showing a filtered down view, we want to explain how many devices you
|
||||
// filtered down to and how many total unfilterd devices there are total.
|
||||
if (overviewViewer.unfilteredAppSessions.totalCount != appSessions.totalCount) {
|
||||
deviceHeaderText = t("frontend.user_sessions_overview.num_devices_filtered_header", {
|
||||
filtered_count: appSessions.totalCount,
|
||||
total_count: overviewViewer.unfilteredAppSessions.totalCount,
|
||||
});
|
||||
if (
|
||||
overviewViewer.unfilteredAppSessions.totalCount != appSessions.totalCount
|
||||
) {
|
||||
deviceHeaderText = t(
|
||||
"frontend.user_sessions_overview.num_devices_filtered_header",
|
||||
{
|
||||
filtered_count: appSessions.totalCount,
|
||||
total_count: overviewViewer.unfilteredAppSessions.totalCount,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// TODO
|
||||
let todoSessionLimit = null;
|
||||
if (sessionLimit) {
|
||||
todoSessionLimit = (
|
||||
<div>TODO: Limited to {sessionLimit.softLimit} devices</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -177,6 +200,7 @@ function Sessions(): React.ReactElement {
|
||||
<BrowserSessionsOverview user={overviewViewer} />
|
||||
|
||||
<H4>{deviceHeaderText}</H4>
|
||||
{todoSessionLimit}
|
||||
<Separator kind="section" />
|
||||
<div className="flex gap-2 justify-start items-center">
|
||||
<Filter
|
||||
|
||||
Reference in New Issue
Block a user