mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
1 Commits
editor/inl
...
web/login-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adebb59df4 |
@@ -231,7 +231,8 @@ import {
|
||||
mdiNotePlus,
|
||||
mdiNoteEditOutline,
|
||||
mdiArrowUp,
|
||||
mdiInbox
|
||||
mdiInbox,
|
||||
mdiMonitor
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -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);
|
||||
|
||||
@@ -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[] = [
|
||||
{
|
||||
@@ -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(),
|
||||
|
||||
187
apps/web/src/dialogs/user-sessions-dialog.tsx
Normal file
187
apps/web/src/dialogs/user-sessions-dialog.tsx
Normal file
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
|
||||
@@ -2605,5 +2605,10 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
clickToDirectlyClaimPromo: () =>
|
||||
t`Click here to directly claim the promotion.`,
|
||||
loginToUploadAttachments: () =>
|
||||
t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)`
|
||||
t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)`,
|
||||
loginSessions: () => t`Login sessions`,
|
||||
loginSessionsDesc: () => t`View and manage your active login sessions.`,
|
||||
viewSessions: () => t`View sessions`,
|
||||
sessionLoggedOut: () => t`Session logged out`,
|
||||
failedToLogoutSession: () => t`Failed to log out session.`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user