You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Cleanup the session details fragments & load last active IP
This also cleans up the GraphQL scalar types, by making sure we always parse dates correctly
This commit is contained in:
@ -21,6 +21,14 @@ const config: CodegenConfig = {
|
||||
generates: {
|
||||
"./src/gql/": {
|
||||
preset: "client",
|
||||
config: {
|
||||
// By default, unknown scalars are generated as `any`. This is not ideal for catching potential bugs.
|
||||
defaultScalarType: "unknown",
|
||||
scalars: {
|
||||
DateTime: "string",
|
||||
Url: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"./src/gql/schema.ts": {
|
||||
plugins: ["urql-introspection"],
|
||||
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
@ -29,12 +30,14 @@ import { useCurrentBrowserSessionId } from "../utils/session/useCurrentBrowserSe
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
import Session from "./Session/Session";
|
||||
|
||||
export const BROWSER_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment BrowserSession_session on BrowserSession {
|
||||
id
|
||||
createdAt
|
||||
finishedAt
|
||||
userAgent
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
lastAuthentication {
|
||||
id
|
||||
createdAt
|
||||
@ -92,17 +95,21 @@ export const useEndBrowserSession = (
|
||||
};
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof BROWSER_SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const BrowserSession: React.FC<Props> = ({ session }) => {
|
||||
const currentBrowserSessionId = useCurrentBrowserSessionId();
|
||||
const data = useFragment(BROWSER_SESSION_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const isCurrent = data.id === currentBrowserSessionId;
|
||||
|
||||
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
|
||||
|
||||
const createdAt = data.createdAt;
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
const deviceInformation = parseUserAgent(data.userAgent || undefined);
|
||||
const sessionName =
|
||||
sessionNameFromDeviceInformation(deviceInformation) || "Browser session";
|
||||
@ -115,8 +122,10 @@ const BrowserSession: React.FC<Props> = ({ session }) => {
|
||||
id={data.id}
|
||||
name={name}
|
||||
createdAt={createdAt}
|
||||
finishedAt={data.finishedAt}
|
||||
finishedAt={finishedAt}
|
||||
isCurrent={isCurrent}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
|
@ -70,7 +70,7 @@ const OAuth2ClientDetail: React.FC<Props> = ({ client }) => {
|
||||
<BlockList>
|
||||
<header className={styles.header}>
|
||||
<ClientAvatar
|
||||
logoUri={data.logoUri}
|
||||
logoUri={data.logoUri || undefined}
|
||||
name={data.clientName || data.clientId}
|
||||
size="1.5rem"
|
||||
/>
|
||||
|
@ -17,28 +17,30 @@
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, beforeAll } from "vitest";
|
||||
|
||||
import { FragmentType } from "../gql/fragment-masking";
|
||||
import { makeFragmentData } from "../gql";
|
||||
import { WithLocation } from "../test-utils/WithLocation";
|
||||
import { mockLocale } from "../test-utils/mockLocale";
|
||||
|
||||
import CompatSession, { COMPAT_SESSION_FRAGMENT } from "./CompatSession";
|
||||
import CompatSession, { FRAGMENT } from "./CompatSession";
|
||||
|
||||
describe("<CompatSession />", () => {
|
||||
const session = {
|
||||
const baseSession = {
|
||||
id: "session-id",
|
||||
deviceId: "abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
lastActiveIp: "1.2.3.4",
|
||||
ssoLogin: {
|
||||
id: "test-id",
|
||||
redirectUri: "https://element.io",
|
||||
},
|
||||
} as FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
|
||||
};
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
|
||||
beforeAll(() => mockLocale());
|
||||
|
||||
it("renders an active session", () => {
|
||||
const session = makeFragmentData(baseSession, FRAGMENT);
|
||||
const component = create(
|
||||
<WithLocation>
|
||||
<CompatSession session={session} />
|
||||
@ -48,13 +50,16 @@ describe("<CompatSession />", () => {
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const finishedSession = {
|
||||
...session,
|
||||
const session = makeFragmentData(
|
||||
{
|
||||
...baseSession,
|
||||
finishedAt,
|
||||
};
|
||||
},
|
||||
FRAGMENT,
|
||||
);
|
||||
const component = create(
|
||||
<WithLocation>
|
||||
<CompatSession session={finishedSession} />
|
||||
<CompatSession session={session} />
|
||||
</WithLocation>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
@ -22,12 +23,14 @@ import { Link } from "../routing";
|
||||
import { Session } from "./Session";
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
|
||||
export const COMPAT_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_session on CompatSession {
|
||||
id
|
||||
createdAt
|
||||
deviceId
|
||||
finishedAt
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
ssoLogin {
|
||||
id
|
||||
redirectUri
|
||||
@ -81,9 +84,9 @@ export const simplifyUrl = (url: string): string => {
|
||||
};
|
||||
|
||||
const CompatSession: React.FC<{
|
||||
session: FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
}> = ({ session }) => {
|
||||
const data = useFragment(COMPAT_SESSION_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const endCompatSession = useSetAtom(endCompatSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = async (): Promise<void> => {
|
||||
@ -98,13 +101,21 @@ const CompatSession: React.FC<{
|
||||
? simplifyUrl(data.ssoLogin.redirectUri)
|
||||
: undefined;
|
||||
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={sessionName}
|
||||
createdAt={data.createdAt}
|
||||
finishedAt={data.finishedAt || undefined}
|
||||
createdAt={createdAt}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
|
@ -17,26 +17,25 @@
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, beforeAll } from "vitest";
|
||||
|
||||
import { FragmentType } from "../gql/fragment-masking";
|
||||
import { makeFragmentData } from "../gql";
|
||||
import { WithLocation } from "../test-utils/WithLocation";
|
||||
import { mockLocale } from "../test-utils/mockLocale";
|
||||
|
||||
import OAuth2Session, { OAUTH2_SESSION_FRAGMENT } from "./OAuth2Session";
|
||||
import OAuth2Session, { FRAGMENT } from "./OAuth2Session";
|
||||
|
||||
describe("<OAuth2Session />", () => {
|
||||
const defaultProps = {
|
||||
session: {
|
||||
const defaultSession = {
|
||||
id: "session-id",
|
||||
scope:
|
||||
"openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
lastActiveIp: "1.2.3.4",
|
||||
client: {
|
||||
id: "test-id",
|
||||
clientId: "test-client-id",
|
||||
clientName: "Element",
|
||||
clientUri: "https://element.io",
|
||||
},
|
||||
} as FragmentType<typeof OAUTH2_SESSION_FRAGMENT>,
|
||||
};
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
@ -44,22 +43,27 @@ describe("<OAuth2Session />", () => {
|
||||
beforeAll(() => mockLocale());
|
||||
|
||||
it("renders an active session", () => {
|
||||
const session = makeFragmentData(defaultSession, FRAGMENT);
|
||||
|
||||
const component = create(
|
||||
<WithLocation>
|
||||
<OAuth2Session {...defaultProps} />
|
||||
<OAuth2Session session={session} />
|
||||
</WithLocation>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const finishedSession = {
|
||||
...defaultProps.session,
|
||||
const session = makeFragmentData(
|
||||
{
|
||||
...defaultSession,
|
||||
finishedAt,
|
||||
};
|
||||
},
|
||||
FRAGMENT,
|
||||
);
|
||||
const component = create(
|
||||
<WithLocation>
|
||||
<OAuth2Session session={finishedSession} />
|
||||
<OAuth2Session session={session} />
|
||||
</WithLocation>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
@ -23,36 +24,23 @@ import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
|
||||
import { Session } from "./Session";
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
|
||||
export const OAUTH2_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment OAuth2Session_session on Oauth2Session {
|
||||
id
|
||||
scope
|
||||
createdAt
|
||||
finishedAt
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
client {
|
||||
id
|
||||
clientId
|
||||
clientName
|
||||
clientUri
|
||||
logoUri
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type Oauth2SessionType = {
|
||||
id: string;
|
||||
scope: string;
|
||||
createdAt: string;
|
||||
finishedAt: string | null;
|
||||
client: {
|
||||
id: string;
|
||||
clientId: string;
|
||||
clientName: string;
|
||||
clientUri: string;
|
||||
logoUri: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation EndOAuth2Session($id: ID!) {
|
||||
endOauth2Session(input: { oauth2SessionId: $id }) {
|
||||
@ -78,14 +66,11 @@ export const endSessionFamily = atomFamily((id: string) => {
|
||||
});
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof OAUTH2_SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
const data = useFragment(
|
||||
OAUTH2_SESSION_FRAGMENT,
|
||||
session,
|
||||
) as Oauth2SessionType;
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const endSession = useSetAtom(endSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = async (): Promise<void> => {
|
||||
@ -98,14 +83,22 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
<Link route={{ type: "session", id: deviceId }}>{deviceId}</Link>
|
||||
);
|
||||
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={name}
|
||||
createdAt={data.createdAt}
|
||||
finishedAt={data.finishedAt || undefined}
|
||||
clientName={data.client.clientName}
|
||||
createdAt={createdAt}
|
||||
finishedAt={finishedAt}
|
||||
clientName={data.client.clientName || data.client.clientId || undefined}
|
||||
clientLogoUri={data.client.logoUri || undefined}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
|
@ -13,40 +13,44 @@
|
||||
// limitations under the License.
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { parseISO, subDays, subHours } from "date-fns";
|
||||
|
||||
import LastActive from "./LastActive";
|
||||
|
||||
type Props = {
|
||||
lastActiveTimestamp: number;
|
||||
now: number;
|
||||
};
|
||||
const Template: React.FC<Props> = ({ lastActiveTimestamp, now }) => {
|
||||
return <LastActive lastActiveTimestamp={lastActiveTimestamp} now={now} />;
|
||||
};
|
||||
|
||||
const meta = {
|
||||
title: "UI/Session/Last active time",
|
||||
component: Template,
|
||||
component: LastActive,
|
||||
argTypes: {
|
||||
lastActive: { control: { type: "date" } },
|
||||
now: { control: { type: "date" } },
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Template>;
|
||||
} satisfies Meta<typeof LastActive>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Template>;
|
||||
type Story = StoryObj<typeof LastActive>;
|
||||
|
||||
const now = 1694999531800;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const now = parseISO("2023-09-18T01:12:00.000Z");
|
||||
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
// yesterday
|
||||
lastActiveTimestamp: now - ONE_DAY_MS,
|
||||
// An hour ago
|
||||
lastActive: subHours(now, 1),
|
||||
now,
|
||||
},
|
||||
};
|
||||
|
||||
export const ActiveThreeDaysAgo: Story = {
|
||||
args: {
|
||||
// Three days ago
|
||||
lastActive: subDays(now, 3),
|
||||
now,
|
||||
},
|
||||
};
|
||||
|
||||
export const ActiveNow: Story = {
|
||||
args: {
|
||||
lastActiveTimestamp: now - 1000,
|
||||
lastActive: now,
|
||||
now,
|
||||
},
|
||||
};
|
||||
@ -54,7 +58,7 @@ export const ActiveNow: Story = {
|
||||
export const Inactive: Story = {
|
||||
args: {
|
||||
// 91 days ago
|
||||
lastActiveTimestamp: now - 91 * ONE_DAY_MS,
|
||||
lastActive: subDays(now, 91),
|
||||
now,
|
||||
},
|
||||
};
|
||||
|
@ -20,7 +20,12 @@ import { describe, afterEach, expect, it, beforeAll } from "vitest";
|
||||
|
||||
import { mockLocale } from "../../test-utils/mockLocale";
|
||||
|
||||
import Meta, { ActiveNow, Basic, Inactive } from "./LastActive.stories";
|
||||
import Meta, {
|
||||
ActiveNow,
|
||||
ActiveThreeDaysAgo,
|
||||
Basic,
|
||||
Inactive,
|
||||
} from "./LastActive.stories";
|
||||
|
||||
describe("<LastActive", () => {
|
||||
beforeAll(() => mockLocale());
|
||||
@ -33,6 +38,12 @@ describe("<LastActive", () => {
|
||||
});
|
||||
|
||||
it("renders a default timestamp", () => {
|
||||
const Component = composeStory(ActiveThreeDaysAgo, Meta);
|
||||
const { container } = render(<Component />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a relative timestamp", () => {
|
||||
const Component = composeStory(Basic, Meta);
|
||||
const { container } = render(<Component />);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
@ -12,35 +12,44 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { differenceInSeconds, parseISO } from "date-fns";
|
||||
|
||||
import { formatDate, formatReadableDate } from "../DateTime";
|
||||
|
||||
import styles from "./LastActive.module.css";
|
||||
|
||||
// 3 minutes
|
||||
const ACTIVE_NOW_MAX_AGE = 1000 * 60 * 3;
|
||||
const ACTIVE_NOW_MAX_AGE = 60 * 3;
|
||||
/// 90 days
|
||||
const INACTIVE_MIN_AGE = 1000 * 60 * 60 * 24 * 90;
|
||||
const INACTIVE_MIN_AGE = 60 * 60 * 24 * 90;
|
||||
|
||||
const LastActive: React.FC<{ lastActiveTimestamp: number; now?: number }> = ({
|
||||
lastActiveTimestamp,
|
||||
now: nowProps,
|
||||
}) => {
|
||||
const now = nowProps || Date.now();
|
||||
const formattedDate = formatDate(new Date(lastActiveTimestamp));
|
||||
if (lastActiveTimestamp >= now - ACTIVE_NOW_MAX_AGE) {
|
||||
const LastActive: React.FC<{
|
||||
lastActive: Date | string;
|
||||
now?: Date | string;
|
||||
}> = ({ lastActive: lastActiveProps, now: nowProps }) => {
|
||||
const lastActive =
|
||||
typeof lastActiveProps === "string"
|
||||
? parseISO(lastActiveProps)
|
||||
: lastActiveProps;
|
||||
|
||||
const now = nowProps
|
||||
? typeof nowProps === "string"
|
||||
? parseISO(nowProps)
|
||||
: nowProps
|
||||
: new Date();
|
||||
|
||||
const formattedDate = formatDate(lastActive);
|
||||
if (differenceInSeconds(now, lastActive) <= ACTIVE_NOW_MAX_AGE) {
|
||||
return (
|
||||
<span title={formattedDate} className={styles.active}>
|
||||
Active now
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (lastActiveTimestamp < now - INACTIVE_MIN_AGE) {
|
||||
if (differenceInSeconds(now, lastActive) > INACTIVE_MIN_AGE) {
|
||||
return <span title={formattedDate}>Inactive for 90+ days</span>;
|
||||
}
|
||||
const relativeDate = formatReadableDate(
|
||||
new Date(lastActiveTimestamp),
|
||||
new Date(now),
|
||||
);
|
||||
const relativeDate = formatReadableDate(lastActive, now);
|
||||
return <span title={formattedDate}>{`Active ${relativeDate}`}</span>;
|
||||
};
|
||||
|
||||
|
@ -14,20 +14,24 @@
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { parseISO } from "date-fns";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
import BlockList from "../BlockList/BlockList";
|
||||
|
||||
import Session, { SessionProps } from "./Session";
|
||||
|
||||
const Template: React.FC<React.PropsWithChildren<SessionProps>> = (props) => {
|
||||
return <Session {...props} />;
|
||||
};
|
||||
import Session from "./Session";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Session/Session",
|
||||
component: Template,
|
||||
component: Session,
|
||||
tags: ["autodocs"],
|
||||
|
||||
argTypes: {
|
||||
createdAt: { control: { type: "date" } },
|
||||
finishedAt: { control: { type: "date" } },
|
||||
lastActiveAt: { control: { type: "date" } },
|
||||
},
|
||||
|
||||
decorators: [
|
||||
(Story): ReactElement => (
|
||||
<div style={{ width: "378px" }}>
|
||||
@ -37,21 +41,22 @@ const meta = {
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Template>;
|
||||
} satisfies Meta<typeof Session>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Template>;
|
||||
type Story = StoryObj<typeof Session>;
|
||||
|
||||
const defaultProps = {
|
||||
id: "oauth2_session:01H5VAGA5NYTKJVXP3HMMKDJQ0",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
createdAt: parseISO("2023-06-29T03:35:17.451292+00:00"),
|
||||
};
|
||||
|
||||
export const BasicSession: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "KlTqK9CRt3",
|
||||
ipAddress: "2001:8003:c4614:f501:3091:888a:49c7",
|
||||
lastActiveIp: "2001:8003:c4614:f501:3091:888a:49c7",
|
||||
lastActiveAt: parseISO("2023-07-29T03:35:17.451292+00:00"),
|
||||
clientName: "Element",
|
||||
},
|
||||
};
|
||||
@ -60,7 +65,7 @@ export const BasicFinishedSession: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "Chrome on Android",
|
||||
finishedAt: "2023-06-30T03:35:17.451292+00:00",
|
||||
finishedAt: parseISO("2023-06-30T03:35:17.451292+00:00"),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, beforeAll } from "vitest";
|
||||
|
||||
@ -22,10 +23,10 @@ import Session from "./Session";
|
||||
describe("<Session />", () => {
|
||||
const defaultProps = {
|
||||
id: "session-id",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
createdAt: parseISO("2023-06-29T03:35:17.451292+00:00"),
|
||||
};
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
const finishedAt = parseISO("2023-06-29T03:35:19.451292+00:00");
|
||||
|
||||
beforeAll(() => mockLocale());
|
||||
|
||||
@ -69,7 +70,7 @@ describe("<Session />", () => {
|
||||
{...defaultProps}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
ipAddress="127.0.0.1"
|
||||
lastActiveIp="127.0.0.1"
|
||||
/>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
|
@ -19,21 +19,23 @@ import Block from "../Block";
|
||||
import DateTime from "../DateTime";
|
||||
|
||||
import ClientAvatar from "./ClientAvatar";
|
||||
import LastActive from "./LastActive";
|
||||
import styles from "./Session.module.css";
|
||||
|
||||
const SessionMetadata: React.FC<React.ComponentProps<typeof Body>> = (
|
||||
props,
|
||||
) => <Body {...props} size="sm" className={styles.sessionMetadata} />;
|
||||
|
||||
export type SessionProps = {
|
||||
type SessionProps = {
|
||||
id: string;
|
||||
name?: string | ReactNode;
|
||||
createdAt: string;
|
||||
finishedAt?: string;
|
||||
createdAt: Date;
|
||||
finishedAt?: Date;
|
||||
clientName?: string;
|
||||
clientLogoUri?: string;
|
||||
isCurrent?: boolean;
|
||||
ipAddress?: string;
|
||||
lastActiveIp?: string;
|
||||
lastActiveAt?: Date;
|
||||
};
|
||||
const Session: React.FC<React.PropsWithChildren<SessionProps>> = ({
|
||||
id,
|
||||
@ -42,7 +44,8 @@ const Session: React.FC<React.PropsWithChildren<SessionProps>> = ({
|
||||
finishedAt,
|
||||
clientName,
|
||||
clientLogoUri,
|
||||
ipAddress,
|
||||
lastActiveIp,
|
||||
lastActiveAt,
|
||||
isCurrent,
|
||||
children,
|
||||
}) => {
|
||||
@ -60,7 +63,12 @@ const Session: React.FC<React.PropsWithChildren<SessionProps>> = ({
|
||||
Finished <DateTime datetime={finishedAt} />
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!ipAddress && <SessionMetadata>{ipAddress}</SessionMetadata>}
|
||||
{!!lastActiveAt && (
|
||||
<SessionMetadata>
|
||||
<LastActive lastActive={lastActiveAt} />
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!lastActiveIp && <SessionMetadata>{lastActiveIp}</SessionMetadata>}
|
||||
{!!clientName && (
|
||||
<SessionMetadata>
|
||||
<ClientAvatar
|
||||
|
@ -3,9 +3,19 @@
|
||||
exports[`<LastActive > renders a default timestamp 1`] = `
|
||||
<div>
|
||||
<span
|
||||
title="Sun, 17 Sept 2023, 01:12"
|
||||
title="Fri, 15 Sept 2023, 01:12"
|
||||
>
|
||||
Active Sun, 17 Sept 2023, 01:12
|
||||
Active Fri, 15 Sept 2023, 01:12
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LastActive > renders a relative timestamp 1`] = `
|
||||
<div>
|
||||
<span
|
||||
title="Mon, 18 Sept 2023, 00:12"
|
||||
>
|
||||
Active 1 hour ago
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Badge } from "@vector-im/compound-web";
|
||||
import { parseISO } from "date-fns";
|
||||
|
||||
import { FragmentType, useFragment } from "../../gql";
|
||||
import { BROWSER_SESSION_DETAIL_FRAGMENT } from "../../pages/BrowserSession";
|
||||
import { FragmentType, graphql, useFragment } from "../../gql";
|
||||
import {
|
||||
parseUserAgent,
|
||||
sessionNameFromDeviceInformation,
|
||||
@ -25,17 +25,37 @@ import BlockList from "../BlockList/BlockList";
|
||||
import { useEndBrowserSession } from "../BrowserSession";
|
||||
import DateTime from "../DateTime";
|
||||
import EndSessionButton from "../Session/EndSessionButton";
|
||||
import LastActive from "../Session/LastActive";
|
||||
|
||||
import styles from "./BrowserSessionDetail.module.css";
|
||||
import SessionDetails from "./SessionDetails";
|
||||
import SessionHeader from "./SessionHeader";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment BrowserSession_detail on BrowserSession {
|
||||
id
|
||||
createdAt
|
||||
finishedAt
|
||||
userAgent
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
lastAuthentication {
|
||||
id
|
||||
createdAt
|
||||
}
|
||||
user {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof BROWSER_SESSION_DETAIL_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const BrowserSessionDetail: React.FC<Props> = ({ session }) => {
|
||||
const data = useFragment(BROWSER_SESSION_DETAIL_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const currentBrowserSessionId = useCurrentBrowserSessionId();
|
||||
|
||||
const isCurrent = currentBrowserSessionId === data.id;
|
||||
@ -46,18 +66,34 @@ const BrowserSessionDetail: React.FC<Props> = ({ session }) => {
|
||||
sessionNameFromDeviceInformation(deviceInformation) || "Browser session";
|
||||
|
||||
const finishedAt = data.finishedAt
|
||||
? [{ label: "Finished", value: <DateTime datetime={data.finishedAt} /> }]
|
||||
? [
|
||||
{
|
||||
label: "Finished",
|
||||
value: <DateTime datetime={parseISO(data.finishedAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const ipAddress = data.ipAddress
|
||||
? [{ label: "IP Address", value: <code>{data.ipAddress}</code> }]
|
||||
const lastActiveIp = data.lastActiveIp
|
||||
? [{ label: "IP Address", value: <code>{data.lastActiveIp}</code> }]
|
||||
: [];
|
||||
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? [
|
||||
{
|
||||
label: "Last Active",
|
||||
value: <LastActive lastActive={parseISO(data.lastActiveAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const lastAuthentication = data.lastAuthentication
|
||||
? [
|
||||
{
|
||||
label: "Last Authentication",
|
||||
value: <DateTime datetime={data.lastAuthentication.createdAt} />,
|
||||
value: (
|
||||
<DateTime datetime={parseISO(data.lastAuthentication.createdAt)} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
@ -68,7 +104,8 @@ const BrowserSessionDetail: React.FC<Props> = ({ session }) => {
|
||||
{ label: "User Name", value: <code>{data.user.username}</code> },
|
||||
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
|
||||
...finishedAt,
|
||||
...ipAddress,
|
||||
...lastActiveAt,
|
||||
...lastActiveIp,
|
||||
...lastAuthentication,
|
||||
];
|
||||
|
||||
|
@ -17,18 +17,19 @@
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
import { describe, expect, it, afterEach, beforeAll } from "vitest";
|
||||
|
||||
import { makeFragmentData } from "../../gql/fragment-masking";
|
||||
import { makeFragmentData } from "../../gql";
|
||||
import { WithLocation } from "../../test-utils/WithLocation";
|
||||
import { mockLocale } from "../../test-utils/mockLocale";
|
||||
import { COMPAT_SESSION_FRAGMENT } from "../CompatSession";
|
||||
|
||||
import CompatSessionDetail from "./CompatSessionDetail";
|
||||
import CompatSessionDetail, { FRAGMENT } from "./CompatSessionDetail";
|
||||
|
||||
describe("<CompatSessionDetail>", () => {
|
||||
const baseSession = {
|
||||
id: "session-id",
|
||||
deviceId: "abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
lastActiveIp: "1.2.3.4",
|
||||
lastActiveAt: "2023-07-29T03:35:17.451292+00:00",
|
||||
ssoLogin: {
|
||||
id: "test-id",
|
||||
redirectUri: "https://element.io",
|
||||
@ -39,7 +40,7 @@ describe("<CompatSessionDetail>", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
it("renders a compatability session details", () => {
|
||||
const data = makeFragmentData(baseSession, COMPAT_SESSION_FRAGMENT);
|
||||
const data = makeFragmentData(baseSession, FRAGMENT);
|
||||
|
||||
const { container } = render(
|
||||
<WithLocation>
|
||||
@ -50,16 +51,13 @@ describe("<CompatSessionDetail>", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a compatability session without an ssoLogin redirectUri", () => {
|
||||
it("renders a compatability session without an ssoLogin", () => {
|
||||
const data = makeFragmentData(
|
||||
{
|
||||
...baseSession,
|
||||
ssoLogin: {
|
||||
id: "dfsdjfdk",
|
||||
redirectUri: undefined,
|
||||
ssoLogin: null,
|
||||
},
|
||||
},
|
||||
COMPAT_SESSION_FRAGMENT,
|
||||
FRAGMENT,
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
@ -77,7 +75,7 @@ describe("<CompatSessionDetail>", () => {
|
||||
...baseSession,
|
||||
finishedAt: "2023-07-29T03:35:17.451292+00:00",
|
||||
},
|
||||
COMPAT_SESSION_FRAGMENT,
|
||||
FRAGMENT,
|
||||
);
|
||||
|
||||
const { getByText, queryByText } = render(
|
||||
|
@ -12,28 +12,41 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { useSetAtom } from "jotai";
|
||||
|
||||
import { FragmentType, useFragment } from "../../gql";
|
||||
import { FragmentType, graphql, useFragment } from "../../gql";
|
||||
import BlockList from "../BlockList/BlockList";
|
||||
import {
|
||||
COMPAT_SESSION_FRAGMENT,
|
||||
endCompatSessionFamily,
|
||||
simplifyUrl,
|
||||
} from "../CompatSession";
|
||||
import { endCompatSessionFamily, simplifyUrl } from "../CompatSession";
|
||||
import DateTime from "../DateTime";
|
||||
import ExternalLink from "../ExternalLink/ExternalLink";
|
||||
import EndSessionButton from "../Session/EndSessionButton";
|
||||
import LastActive from "../Session/LastActive";
|
||||
|
||||
import SessionDetails from "./SessionDetails";
|
||||
import SessionHeader from "./SessionHeader";
|
||||
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_detail on CompatSession {
|
||||
id
|
||||
createdAt
|
||||
deviceId
|
||||
finishedAt
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
ssoLogin {
|
||||
id
|
||||
redirectUri
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const CompatSessionDetail: React.FC<Props> = ({ session }) => {
|
||||
const data = useFragment(COMPAT_SESSION_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const endSession = useSetAtom(endCompatSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = async (): Promise<void> => {
|
||||
@ -41,19 +54,37 @@ const CompatSessionDetail: React.FC<Props> = ({ session }) => {
|
||||
};
|
||||
|
||||
const finishedAt = data.finishedAt
|
||||
? [{ label: "Finished", value: <DateTime datetime={data.finishedAt} /> }]
|
||||
? [
|
||||
{
|
||||
label: "Finished",
|
||||
value: <DateTime datetime={parseISO(data.finishedAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const ipAddress = data.ipAddress
|
||||
? [{ label: "IP Address", value: <code>{data.ipAddress}</code> }]
|
||||
const lastActiveIp = data.lastActiveIp
|
||||
? [{ label: "IP Address", value: <code>{data.lastActiveIp}</code> }]
|
||||
: [];
|
||||
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? [
|
||||
{
|
||||
label: "Last Active",
|
||||
value: <LastActive lastActive={parseISO(data.lastActiveAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const sessionDetails = [
|
||||
{ label: "ID", value: <code>{data.id}</code> },
|
||||
{ label: "Device ID", value: <code>{data.deviceId}</code> },
|
||||
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
|
||||
{
|
||||
label: "Signed in",
|
||||
value: <DateTime datetime={parseISO(data.createdAt)} />,
|
||||
},
|
||||
...finishedAt,
|
||||
...ipAddress,
|
||||
...lastActiveAt,
|
||||
...lastActiveIp,
|
||||
];
|
||||
|
||||
const clientDetails: { label: string; value: string | JSX.Element }[] = [];
|
||||
|
@ -17,12 +17,11 @@
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
import { describe, expect, it, afterEach, beforeAll } from "vitest";
|
||||
|
||||
import { makeFragmentData } from "../../gql/fragment-masking";
|
||||
import { makeFragmentData } from "../../gql";
|
||||
import { WithLocation } from "../../test-utils/WithLocation";
|
||||
import { mockLocale } from "../../test-utils/mockLocale";
|
||||
import { OAUTH2_SESSION_FRAGMENT } from "../OAuth2Session";
|
||||
|
||||
import OAuth2SessionDetail from "./OAuth2SessionDetail";
|
||||
import OAuth2SessionDetail, { FRAGMENT } from "./OAuth2SessionDetail";
|
||||
|
||||
describe("<OAuth2SessionDetail>", () => {
|
||||
const baseSession = {
|
||||
@ -30,6 +29,8 @@ describe("<OAuth2SessionDetail>", () => {
|
||||
scope:
|
||||
"openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
lastActiveAt: "2023-07-29T03:35:17.451292+00:00",
|
||||
lastActiveIp: "1.2.3.4",
|
||||
client: {
|
||||
id: "test-id",
|
||||
clientId: "test-client-id",
|
||||
@ -42,7 +43,7 @@ describe("<OAuth2SessionDetail>", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
it("renders session details", () => {
|
||||
const data = makeFragmentData(baseSession, OAUTH2_SESSION_FRAGMENT);
|
||||
const data = makeFragmentData(baseSession, FRAGMENT);
|
||||
|
||||
const { container } = render(
|
||||
<WithLocation>
|
||||
@ -59,7 +60,7 @@ describe("<OAuth2SessionDetail>", () => {
|
||||
...baseSession,
|
||||
finishedAt: "2023-07-29T03:35:17.451292+00:00",
|
||||
},
|
||||
OAUTH2_SESSION_FRAGMENT,
|
||||
FRAGMENT,
|
||||
);
|
||||
|
||||
const { getByText, queryByText } = render(
|
||||
|
@ -12,26 +12,46 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { useSetAtom } from "jotai";
|
||||
|
||||
import { FragmentType, useFragment } from "../../gql";
|
||||
import { FragmentType, graphql, useFragment } from "../../gql";
|
||||
import { Link } from "../../routing";
|
||||
import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope";
|
||||
import BlockList from "../BlockList/BlockList";
|
||||
import DateTime from "../DateTime";
|
||||
import { OAUTH2_SESSION_FRAGMENT, endSessionFamily } from "../OAuth2Session";
|
||||
import { endSessionFamily } from "../OAuth2Session";
|
||||
import ClientAvatar from "../Session/ClientAvatar";
|
||||
import EndSessionButton from "../Session/EndSessionButton";
|
||||
import LastActive from "../Session/LastActive";
|
||||
|
||||
import SessionDetails from "./SessionDetails";
|
||||
import SessionHeader from "./SessionHeader";
|
||||
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment OAuth2Session_detail on Oauth2Session {
|
||||
id
|
||||
scope
|
||||
createdAt
|
||||
finishedAt
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
client {
|
||||
id
|
||||
clientId
|
||||
clientName
|
||||
clientUri
|
||||
logoUri
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof OAUTH2_SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
|
||||
const data = useFragment(OAUTH2_SESSION_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const endSession = useSetAtom(endSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = async (): Promise<void> => {
|
||||
@ -43,11 +63,25 @@ const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
|
||||
const scopes = data.scope.split(" ");
|
||||
|
||||
const finishedAt = data.finishedAt
|
||||
? [{ label: "Finished", value: <DateTime datetime={data.createdAt} /> }]
|
||||
? [
|
||||
{
|
||||
label: "Finished",
|
||||
value: <DateTime datetime={parseISO(data.createdAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const ipAddress = data.ipAddress
|
||||
? [{ label: "IP Address", value: <code>{data.ipAddress}</code> }]
|
||||
const lastActiveIp = data.lastActiveIp
|
||||
? [{ label: "IP Address", value: <code>{data.lastActiveIp}</code> }]
|
||||
: [];
|
||||
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? [
|
||||
{
|
||||
label: "Last Active",
|
||||
value: <LastActive lastActive={parseISO(data.lastActiveAt)} />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const sessionDetails = [
|
||||
@ -55,15 +89,16 @@ const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
|
||||
{ label: "Device ID", value: <code>{deviceId}</code> },
|
||||
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
|
||||
...finishedAt,
|
||||
...ipAddress,
|
||||
...lastActiveAt,
|
||||
...lastActiveIp,
|
||||
{
|
||||
label: "Scopes",
|
||||
value: (
|
||||
<div>
|
||||
<span>
|
||||
{scopes.map((scope) => (
|
||||
<code key={scope}>{scope}</code>
|
||||
))}
|
||||
</div>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
@ -89,7 +124,7 @@ const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
|
||||
{
|
||||
label: "Uri",
|
||||
value: (
|
||||
<a target="_blank" href={data.client.clientUri}>
|
||||
<a target="_blank" href={data.client.clientUri || undefined}>
|
||||
{data.client.clientUri}
|
||||
</a>
|
||||
),
|
||||
|
@ -28,8 +28,8 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
query SessionQuery($userId: ID!, $deviceId: String!) {
|
||||
session(userId: $userId, deviceId: $deviceId) {
|
||||
__typename
|
||||
...CompatSession_session
|
||||
...OAuth2Session_session
|
||||
...CompatSession_detail
|
||||
...OAuth2Session_detail
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -45,6 +45,11 @@ const sessionFamily = atomFamily(
|
||||
},
|
||||
);
|
||||
|
||||
// A type-safe way to ensure we've handled all session types
|
||||
const unknownSessionType = (type: never): never => {
|
||||
throw new Error(`Unknown session type: ${type}`);
|
||||
};
|
||||
|
||||
const SessionDetail: React.FC<{
|
||||
deviceId: string;
|
||||
userId: string;
|
||||
@ -70,10 +75,13 @@ const SessionDetail: React.FC<{
|
||||
|
||||
const sessionType = session.__typename;
|
||||
|
||||
if (sessionType === "Oauth2Session") {
|
||||
return <OAuth2SessionDetail session={session} />;
|
||||
} else {
|
||||
switch (sessionType) {
|
||||
case "CompatSession":
|
||||
return <CompatSessionDetail session={session} />;
|
||||
case "Oauth2Session":
|
||||
return <OAuth2SessionDetail session={session} />;
|
||||
default:
|
||||
unknownSessionType(sessionType);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -94,6 +94,40 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
|
||||
</time>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
Last Active
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<span
|
||||
title="Sat, 29 Jul 2023, 03:35"
|
||||
>
|
||||
Active Sat, 29 Jul 2023, 03:35
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
IP Address
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<code>
|
||||
1.2.3.4
|
||||
</code>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
@ -163,7 +197,7 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<CompatSessionDetail> > renders a compatability session without an ssoLogin redirectUri 1`] = `
|
||||
exports[`<CompatSessionDetail> > renders a compatability session without an ssoLogin 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_blockList_f8cc7f"
|
||||
@ -257,6 +291,40 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
|
||||
</time>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
Last Active
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<span
|
||||
title="Sat, 29 Jul 2023, 03:35"
|
||||
>
|
||||
Active Sat, 29 Jul 2023, 03:35
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
IP Address
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<code>
|
||||
1.2.3.4
|
||||
</code>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
|
@ -94,6 +94,40 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
</time>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
Last Active
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<span
|
||||
title="Sat, 29 Jul 2023, 03:35"
|
||||
>
|
||||
Active Sat, 29 Jul 2023, 03:35
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
<p
|
||||
class="_font-body-sm-semibold_1jx6b_45 _detailLabel_040867"
|
||||
>
|
||||
IP Address
|
||||
</p>
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<code>
|
||||
1.2.3.4
|
||||
</code>
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_detailRow_040867"
|
||||
>
|
||||
@ -105,7 +139,7 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
<p
|
||||
class="_font-body-sm-regular_1jx6b_40 _detailValue_040867"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<code>
|
||||
openid
|
||||
</code>
|
||||
@ -115,7 +149,7 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
<code>
|
||||
urn:matrix:org.matrix.msc2967.client:device:abcd1234
|
||||
</code>
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -37,6 +37,11 @@ exports[`<CompatSession /> > renders a finished session 1`] = `
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
@ -76,6 +81,11 @@ exports[`<CompatSession /> > renders an active session 1`] = `
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
@ -37,6 +37,11 @@ exports[`<OAuth2Session /> > renders a finished session 1`] = `
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
@ -76,6 +81,11 @@ exports[`<OAuth2Session /> > renders an active session 1`] = `
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1jx6b_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
@ -17,7 +17,7 @@ const documents = {
|
||||
types.CurrentViewerQueryDocument,
|
||||
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
||||
types.CurrentViewerSessionQueryDocument,
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||
types.BrowserSession_SessionFragmentDoc,
|
||||
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
|
||||
types.EndBrowserSessionDocument,
|
||||
@ -25,15 +25,21 @@ const documents = {
|
||||
types.BrowserSessionListDocument,
|
||||
"\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n":
|
||||
types.OAuth2Client_DetailFragmentDoc,
|
||||
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n redirectUri\n }\n }\n":
|
||||
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n":
|
||||
types.CompatSession_SessionFragmentDoc,
|
||||
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n":
|
||||
types.EndCompatSessionDocument,
|
||||
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n":
|
||||
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n logoUri\n }\n }\n":
|
||||
types.OAuth2Session_SessionFragmentDoc,
|
||||
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
|
||||
types.EndOAuth2SessionDocument,
|
||||
"\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n":
|
||||
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n":
|
||||
types.BrowserSession_DetailFragmentDoc,
|
||||
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n":
|
||||
types.CompatSession_DetailFragmentDoc,
|
||||
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n":
|
||||
types.OAuth2Session_DetailFragmentDoc,
|
||||
"\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n }\n }\n":
|
||||
types.SessionQueryDocument,
|
||||
"\n fragment UnverifiedEmailAlert on User {\n id\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n }\n":
|
||||
types.UnverifiedEmailAlertFragmentDoc,
|
||||
@ -63,8 +69,6 @@ const documents = {
|
||||
types.VerifyEmailDocument,
|
||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.ResendVerificationEmailDocument,
|
||||
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n":
|
||||
types.BrowserSession_DetailFragmentDoc,
|
||||
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n ...BrowserSession_detail\n }\n }\n":
|
||||
types.BrowserSessionQueryDocument,
|
||||
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
|
||||
@ -105,8 +109,8 @@ export function graphql(
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n }\n",
|
||||
): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n }\n"];
|
||||
source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n",
|
||||
): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -129,8 +133,8 @@ export function graphql(
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n redirectUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n redirectUri\n }\n }\n"];
|
||||
source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -141,8 +145,8 @@ export function graphql(
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n"];
|
||||
source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n logoUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n logoUri\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -153,8 +157,26 @@ export function graphql(
|
||||
* 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 SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n",
|
||||
): (typeof documents)["\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n"];
|
||||
source: "\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n",
|
||||
): (typeof documents)["\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ssoLogin {\n id\n redirectUri\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n"];
|
||||
/**
|
||||
* 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 SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n }\n }\n",
|
||||
): (typeof documents)["\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -239,12 +261,6 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n",
|
||||
): (typeof documents)["\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
@ -32,9 +32,9 @@ export type Scalars = {
|
||||
*
|
||||
* The input/output is a string in RFC3339 format.
|
||||
*/
|
||||
DateTime: { input: any; output: any };
|
||||
DateTime: { input: string; output: string };
|
||||
/** URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/) */
|
||||
Url: { input: any; output: any };
|
||||
Url: { input: string; output: string };
|
||||
};
|
||||
|
||||
/** The input for the `addEmail` mutation */
|
||||
@ -1083,13 +1083,15 @@ export type CurrentViewerSessionQueryQuery = {
|
||||
export type BrowserSession_SessionFragment = {
|
||||
__typename?: "BrowserSession";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
finishedAt?: any | null;
|
||||
createdAt: string;
|
||||
finishedAt?: string | null;
|
||||
userAgent?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
lastAuthentication?: {
|
||||
__typename?: "Authentication";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
createdAt: string;
|
||||
} | null;
|
||||
} & { " $fragmentName"?: "BrowserSession_SessionFragment" };
|
||||
|
||||
@ -1154,23 +1156,25 @@ export type OAuth2Client_DetailFragment = {
|
||||
id: string;
|
||||
clientId: string;
|
||||
clientName?: string | null;
|
||||
clientUri?: any | null;
|
||||
logoUri?: any | null;
|
||||
tosUri?: any | null;
|
||||
policyUri?: any | null;
|
||||
redirectUris: Array<any>;
|
||||
clientUri?: string | null;
|
||||
logoUri?: string | null;
|
||||
tosUri?: string | null;
|
||||
policyUri?: string | null;
|
||||
redirectUris: Array<string>;
|
||||
} & { " $fragmentName"?: "OAuth2Client_DetailFragment" };
|
||||
|
||||
export type CompatSession_SessionFragment = {
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
createdAt: string;
|
||||
deviceId: string;
|
||||
finishedAt?: any | null;
|
||||
finishedAt?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
ssoLogin?: {
|
||||
__typename?: "CompatSsoLogin";
|
||||
id: string;
|
||||
redirectUri: any;
|
||||
redirectUri: string;
|
||||
} | null;
|
||||
} & { " $fragmentName"?: "CompatSession_SessionFragment" };
|
||||
|
||||
@ -1186,7 +1190,7 @@ export type EndCompatSessionMutation = {
|
||||
compatSession?: {
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
finishedAt?: any | null;
|
||||
finishedAt?: string | null;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
@ -1195,15 +1199,16 @@ export type OAuth2Session_SessionFragment = {
|
||||
__typename?: "Oauth2Session";
|
||||
id: string;
|
||||
scope: string;
|
||||
createdAt: any;
|
||||
finishedAt?: any | null;
|
||||
createdAt: string;
|
||||
finishedAt?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
client: {
|
||||
__typename?: "Oauth2Client";
|
||||
id: string;
|
||||
clientId: string;
|
||||
clientName?: string | null;
|
||||
clientUri?: any | null;
|
||||
logoUri?: any | null;
|
||||
logoUri?: string | null;
|
||||
};
|
||||
} & { " $fragmentName"?: "OAuth2Session_SessionFragment" };
|
||||
|
||||
@ -1226,6 +1231,55 @@ export type EndOAuth2SessionMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type BrowserSession_DetailFragment = {
|
||||
__typename?: "BrowserSession";
|
||||
id: string;
|
||||
createdAt: string;
|
||||
finishedAt?: string | null;
|
||||
userAgent?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
lastAuthentication?: {
|
||||
__typename?: "Authentication";
|
||||
id: string;
|
||||
createdAt: string;
|
||||
} | null;
|
||||
user: { __typename?: "User"; id: string; username: string };
|
||||
} & { " $fragmentName"?: "BrowserSession_DetailFragment" };
|
||||
|
||||
export type CompatSession_DetailFragment = {
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
createdAt: string;
|
||||
deviceId: string;
|
||||
finishedAt?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
ssoLogin?: {
|
||||
__typename?: "CompatSsoLogin";
|
||||
id: string;
|
||||
redirectUri: string;
|
||||
} | null;
|
||||
} & { " $fragmentName"?: "CompatSession_DetailFragment" };
|
||||
|
||||
export type OAuth2Session_DetailFragment = {
|
||||
__typename?: "Oauth2Session";
|
||||
id: string;
|
||||
scope: string;
|
||||
createdAt: string;
|
||||
finishedAt?: string | null;
|
||||
lastActiveIp?: string | null;
|
||||
lastActiveAt?: string | null;
|
||||
client: {
|
||||
__typename?: "Oauth2Client";
|
||||
id: string;
|
||||
clientId: string;
|
||||
clientName?: string | null;
|
||||
clientUri?: string | null;
|
||||
logoUri?: string | null;
|
||||
};
|
||||
} & { " $fragmentName"?: "OAuth2Session_DetailFragment" };
|
||||
|
||||
export type SessionQueryQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
deviceId: Scalars["String"]["input"];
|
||||
@ -1236,12 +1290,12 @@ export type SessionQueryQuery = {
|
||||
session?:
|
||||
| ({ __typename: "CompatSession" } & {
|
||||
" $fragmentRefs"?: {
|
||||
CompatSession_SessionFragment: CompatSession_SessionFragment;
|
||||
CompatSession_DetailFragment: CompatSession_DetailFragment;
|
||||
};
|
||||
})
|
||||
| ({ __typename: "Oauth2Session" } & {
|
||||
" $fragmentRefs"?: {
|
||||
OAuth2Session_SessionFragment: OAuth2Session_SessionFragment;
|
||||
OAuth2Session_DetailFragment: OAuth2Session_DetailFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
@ -1257,7 +1311,7 @@ export type UserEmail_EmailFragment = {
|
||||
__typename?: "UserEmail";
|
||||
id: string;
|
||||
email: string;
|
||||
confirmedAt?: any | null;
|
||||
confirmedAt?: string | null;
|
||||
} & { " $fragmentName"?: "UserEmail_EmailFragment" };
|
||||
|
||||
export type RemoveEmailMutationVariables = Exact<{
|
||||
@ -1505,20 +1559,6 @@ export type ResendVerificationEmailMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type BrowserSession_DetailFragment = {
|
||||
__typename?: "BrowserSession";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
finishedAt?: any | null;
|
||||
userAgent?: string | null;
|
||||
lastAuthentication?: {
|
||||
__typename?: "Authentication";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
} | null;
|
||||
user: { __typename?: "User"; id: string; username: string };
|
||||
} & { " $fragmentName"?: "BrowserSession_DetailFragment" };
|
||||
|
||||
export type BrowserSessionQueryQueryVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
@ -1596,6 +1636,8 @@ export const BrowserSession_SessionFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
@ -1655,6 +1697,8 @@ export const CompatSession_SessionFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
@ -1688,6 +1732,126 @@ export const OAuth2Session_SessionFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "scope" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "client" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientName" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logoUri" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<OAuth2Session_SessionFragment, unknown>;
|
||||
export const BrowserSession_DetailFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "BrowserSession_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "BrowserSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "username" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<BrowserSession_DetailFragment, unknown>;
|
||||
export const CompatSession_DetailFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSession_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "redirectUri" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CompatSession_DetailFragment, unknown>;
|
||||
export const OAuth2Session_DetailFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "OAuth2Session_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "Oauth2Session" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "scope" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "client" },
|
||||
@ -1706,7 +1870,7 @@ export const OAuth2Session_SessionFragmentDoc = {
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<OAuth2Session_SessionFragment, unknown>;
|
||||
} as unknown as DocumentNode<OAuth2Session_DetailFragment, unknown>;
|
||||
export const UnverifiedEmailAlertFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@ -1831,50 +1995,6 @@ export const UserEmail_VerifyEmailFragmentDoc = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UserEmail_VerifyEmailFragment, unknown>;
|
||||
export const BrowserSession_DetailFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "BrowserSession_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "BrowserSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "username" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<BrowserSession_DetailFragment, unknown>;
|
||||
export const CurrentViewerQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@ -2063,6 +2183,8 @@ export const EndBrowserSessionDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
@ -2295,6 +2417,8 @@ export const BrowserSessionListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
@ -2465,6 +2589,8 @@ export const EndOAuth2SessionDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "scope" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "client" },
|
||||
@ -2474,7 +2600,6 @@ export const EndOAuth2SessionDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientName" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientUri" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logoUri" } },
|
||||
],
|
||||
},
|
||||
@ -2551,11 +2676,11 @@ export const SessionQueryDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "__typename" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "CompatSession_session" },
|
||||
name: { kind: "Name", value: "CompatSession_detail" },
|
||||
},
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "OAuth2Session_session" },
|
||||
name: { kind: "Name", value: "OAuth2Session_detail" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -2565,7 +2690,7 @@ export const SessionQueryDocument = {
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSession_session" },
|
||||
name: { kind: "Name", value: "CompatSession_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
@ -2577,6 +2702,8 @@ export const SessionQueryDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
@ -2593,7 +2720,7 @@ export const SessionQueryDocument = {
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "OAuth2Session_session" },
|
||||
name: { kind: "Name", value: "OAuth2Session_detail" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "Oauth2Session" },
|
||||
@ -2605,6 +2732,8 @@ export const SessionQueryDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "scope" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "client" },
|
||||
@ -3563,6 +3692,8 @@ export const AppSessionListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
@ -3591,6 +3722,8 @@ export const AppSessionListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "scope" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "client" },
|
||||
@ -3600,7 +3733,6 @@ export const AppSessionListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientName" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "clientUri" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logoUri" } },
|
||||
],
|
||||
},
|
||||
@ -3907,6 +4039,8 @@ export const BrowserSessionQueryDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveIp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastActiveAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
|
@ -24,23 +24,6 @@ import BrowserSessionDetail from "../components/SessionDetail/BrowserSessionDeta
|
||||
import { graphql } from "../gql";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
export const BROWSER_SESSION_DETAIL_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment BrowserSession_detail on BrowserSession {
|
||||
id
|
||||
createdAt
|
||||
finishedAt
|
||||
userAgent
|
||||
lastAuthentication {
|
||||
id
|
||||
createdAt
|
||||
}
|
||||
user {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query BrowserSessionQuery($id: ID!) {
|
||||
browserSession(id: $id) {
|
||||
|
@ -122,7 +122,7 @@ export default defineConfig((env) => ({
|
||||
proxy: {
|
||||
// Routes mostly extracted from crates/router/src/endpoints.rs
|
||||
"^/(|graphql.*|assets.*|\\.well-known.*|oauth2.*|login.*|logout.*|register.*|reauth.*|add-email.*|verify-email.*|change-password.*|consent.*|_matrix.*|complete-compat-sso.*)$":
|
||||
"https://auth-oidc.lab.element.dev",
|
||||
"http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
|
||||
|
Reference in New Issue
Block a user