Skip to content
Open
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
4 changes: 3 additions & 1 deletion apps/web/src/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ import {
mdiNotePlus,
mdiNoteEditOutline,
mdiArrowUp,
mdiInbox
mdiInbox,
mdiMonitor
} from "@mdi/js";
import { useTheme } from "@emotion/react";
import { Theme } from "@notesnook/theme";
Expand Down Expand Up @@ -586,3 +587,4 @@ export const ExpandSidebar = createIcon(mdiArrowCollapseRight);
export const HamburgerMenu = createIcon(mdiMenu);
export const ArrowUp = createIcon(mdiArrowUp);
export const Inbox = createIcon(mdiInbox);
export const Monitor = createIcon(mdiMonitor);
16 changes: 16 additions & 0 deletions apps/web/src/dialogs/settings/profile-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { EmailChangeDialog } from "../email-change-dialog";
import { RecoveryKeyDialog } from "../recovery-key-dialog";
import { UserProfile } from "./components/user-profile";
import { SettingsGroup } from "./types";
import { UserSessionsDialog } from "../user-sessions-dialog";

export const ProfileSettings: SettingsGroup[] = [
{
Expand Down Expand Up @@ -131,6 +132,21 @@ export const ProfileSettings: SettingsGroup[] = [
},
isHidden: () => !useUserStore.getState().isLoggedIn,
settings: [
{
key: "login-sessions",
title: strings.loginSessions(),
description: strings.loginSessionsDesc(),
components: [
{
type: "button",
title: strings.viewSessions(),
variant: "secondary",
action: async () => {
await UserSessionsDialog.show({});
}
}
]
},
{
key: "logout",
title: strings.logout(),
Expand Down
187 changes: 187 additions & 0 deletions apps/web/src/dialogs/user-sessions-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2023 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { getFormattedDate, usePromise } from "@notesnook/common";
import { DialogManager } from "../common/dialog-manager";
import { db } from "../common/db";
import Dialog from "../components/dialog";
import { Button, Flex, Text } from "@theme-ui/components";
import { Loader } from "../components/loader";
import { ScrollContainer } from "@notesnook/ui";
import { ErrorText } from "../components/error-text";
import { Cellphone, FileWebClip, Monitor } from "../components/icons";
import { logger, UserSession } from "@notesnook/core";
import { showToast } from "../utils/toast";
import { useState } from "react";
import { strings } from "@notesnook/intl";

export const UserSessionsDialog = DialogManager.register(
function UserSessionsDialog(props) {
const sessions = usePromise(() => db.user.getSessions());

return (
<Dialog
isOpen={true}
title={strings.loginSessions()}
description={strings.loginSessionsDesc()}
onClose={() => props.onClose(false)}
negativeButton={{
text: "Close",
onClick: () => props.onClose(false)
}}
width={500}
>
<ScrollContainer
style={{
display: "flex",
flexDirection: "column",
maxHeight: 400
}}
>
{sessions.status === "pending" ? (
<Loader title={strings.loading()} />
) : sessions.status === "rejected" ? (
<ErrorText error={sessions.reason} />
) : !sessions.value ? (
<Flex
sx={{
flexDirection: "column",
alignItems: "center",
py: 4,
color: "paragraph"
}}
>
<Text variant="body">{strings.somethingWentWrong()}</Text>
</Flex>
) : (
<Flex sx={{ flexDirection: "row", gap: 2, flexWrap: "wrap" }}>
{sessions.value.map((session) => (
<SessionItem
key={session.sessionKey}
session={session}
onLogout={sessions.refresh}
/>
))}
</Flex>
)}
</ScrollContainer>
</Dialog>
);
}
);

function SessionItem({
session,
onLogout
}: {
session: UserSession;
onLogout: () => void;
}) {
const [isLoggingOut, setIsLoggingOut] = useState(false);
const type = session.browser?.toLowerCase().includes("electron")
? "desktop"
: session.platform?.toLowerCase().includes("ios") ||
session.platform?.toLowerCase().includes("android")
? "mobile"
: "web";

return (
<Flex
sx={{
flexDirection: "column",
gap: 1,
p: 3,
borderRadius: "default",
border: "1px solid",
borderColor: "border"
}}
>
<Flex sx={{ alignItems: "center", gap: 1 }}>
{type === "desktop" ? (
<Monitor size={16} />
) : type === "mobile" ? (
<Cellphone size={16} />
) : (
<FileWebClip size={16} />
)}
<Text
variant="subtitle"
sx={{
fontWeight: "bold",
color: session.isCurrentDevice ? "accent" : "heading"
}}
>
{session.browser || "Unknown Browser"}
</Text>
{session.isCurrentDevice && (
<Text
variant="subBody"
sx={{
py: 0.8,
px: 1,
color: "accent",
border: "1px solid",
borderColor: "accent",
fontWeight: "bold",
borderRadius: "default"
}}
>
Current Device
</Text>
)}
</Flex>

{session.platform && (
<Text variant="body" sx={{ color: "paragraph" }}>
{session.platform}
</Text>
)}

<Flex sx={{ gap: 2 }}>
<Text variant="subBody" sx={{ color: "fontTertiary" }}>
Created: {getFormattedDate(session.createdAt, "date-time")}
</Text>
</Flex>

{!session.isCurrentDevice && (
<Button
variant="errorSecondary"
onClick={async () => {
try {
setIsLoggingOut(true);
await db.user.logoutSession(session.sessionKey);
showToast("success", strings.sessionLoggedOut());
onLogout();
} catch (e) {
if (e instanceof Error) {
logger.error(e, "Failed to logout session:");
}
showToast("error", strings.failedToLogoutSession());
} finally {
setIsLoggingOut(false);
}
}}
disabled={isLoggingOut}
>
{isLoggingOut ? strings.loggingOut() : strings.logout()}
</Button>
)}
</Flex>
);
}
19 changes: 15 additions & 4 deletions packages/core/src/api/user-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { User } from "../types.js";
import { User, UserSession } from "../types.js";
import http from "../utils/http.js";
import constants from "../utils/constants.js";
import TokenManager from "./token-manager.js";
Expand Down Expand Up @@ -243,10 +243,10 @@ class UserManager {
EV.publish(EVENTS.userLoggedIn, user);
}

async getSessions() {
async getSessions(): Promise<UserSession[]> {
const token = await this.tokenManager.getAccessToken();
if (!token) return;
await http.get(`${constants.AUTH_HOST}/account/sessions`, token);
if (!token) return [];
return await http.get(`${constants.AUTH_HOST}/account/sessions`, token);
}

async clearSessions(all = false) {
Expand Down Expand Up @@ -286,6 +286,17 @@ class UserManager {
}
}

async logoutSession(sessionKey: string) {
const token = await this.tokenManager.getAccessToken();
if (!token) return [];

await http.post(
`${constants.AUTH_HOST}/account/clear-session`,
{ sessionKey },
token
);
}

setUser(user: User) {
return this.db.kv().write("user", user);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,14 @@ export type User = {
};
};

export type UserSession = {
sessionKey: string;
createdAt: number;
isCurrentDevice: boolean;
browser?: string;
platform?: string;
};

export type Profile = {
fullName?: string;
profilePicture?: string;
Expand Down
20 changes: 20 additions & 0 deletions packages/intl/locale/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -2737,6 +2737,10 @@ msgstr "Failed to download file"
msgid "Failed to install theme."
msgstr "Failed to install theme."

#: src/strings.ts:2613
msgid "Failed to log out session."
msgstr "Failed to log out session."

#: src/strings.ts:947
msgid "Failed to open"
msgstr "Failed to open"
Expand Down Expand Up @@ -3691,6 +3695,10 @@ msgstr "Login failed"
msgid "Login required"
msgstr "Login required"

#: src/strings.ts:2609
msgid "Login sessions"
msgstr "Login sessions"

#: src/strings.ts:778
msgid "Login successful"
msgstr "Login successful"
Expand Down Expand Up @@ -5778,6 +5786,10 @@ msgstr "Servers configuration"
msgid "Session expired"
msgstr "Session expired"

#: src/strings.ts:2612
msgid "Session logged out"
msgstr "Session logged out"

#: src/strings.ts:2190
msgid "Sessions"
msgstr "Sessions"
Expand Down Expand Up @@ -7010,6 +7022,10 @@ msgstr "Videos"
msgid "View all linked notebooks"
msgstr "View all linked notebooks"

#: src/strings.ts:2610
msgid "View and manage your active login sessions."
msgstr "View and manage your active login sessions."

#: src/strings.ts:1269
msgid "View and share debug logs"
msgstr "View and share debug logs"
Expand All @@ -7022,6 +7038,10 @@ msgstr "View receipt"
msgid "View recovery codes"
msgstr "View recovery codes"

#: src/strings.ts:2611
msgid "View sessions"
msgstr "View sessions"

#: src/strings.ts:2151
msgid "View source code"
msgstr "View source code"
Expand Down
20 changes: 20 additions & 0 deletions packages/intl/locale/pseudo-LOCALE.po
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,10 @@ msgstr ""
msgid "Failed to install theme."
msgstr ""

#: src/strings.ts:2613
msgid "Failed to log out session."
msgstr ""

#: src/strings.ts:947
msgid "Failed to open"
msgstr ""
Expand Down Expand Up @@ -3671,6 +3675,10 @@ msgstr ""
msgid "Login required"
msgstr ""

#: src/strings.ts:2609
msgid "Login sessions"
msgstr ""

#: src/strings.ts:778
msgid "Login successful"
msgstr ""
Expand Down Expand Up @@ -5752,6 +5760,10 @@ msgstr ""
msgid "Session expired"
msgstr ""

#: src/strings.ts:2612
msgid "Session logged out"
msgstr ""

#: src/strings.ts:2190
msgid "Sessions"
msgstr ""
Expand Down Expand Up @@ -6961,6 +6973,10 @@ msgstr ""
msgid "View all linked notebooks"
msgstr ""

#: src/strings.ts:2610
msgid "View and manage your active login sessions."
msgstr ""

#: src/strings.ts:1269
msgid "View and share debug logs"
msgstr ""
Expand All @@ -6973,6 +6989,10 @@ msgstr ""
msgid "View recovery codes"
msgstr ""

#: src/strings.ts:2611
msgid "View sessions"
msgstr ""

#: src/strings.ts:2151
msgid "View source code"
msgstr ""
Expand Down
Loading
Loading