Cleanup and refactor AuthStore (#6086)
This commit is contained in:
@@ -42,7 +42,7 @@ function Icon({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function LanguagePrompt() {
|
export default function LanguagePrompt() {
|
||||||
const { auth, ui } = useStores();
|
const { ui } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const language = detectLanguage();
|
const language = detectLanguage();
|
||||||
@@ -75,9 +75,7 @@ export default function LanguagePrompt() {
|
|||||||
<Link
|
<Link
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
ui.setLanguagePromptDismissed();
|
ui.setLanguagePromptDismissed();
|
||||||
await auth.updateUser({
|
await user.save({ language });
|
||||||
language,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("Change Language")}
|
{t("Change Language")}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
provider.on("authenticationFailed", () => {
|
provider.on("authenticationFailed", () => {
|
||||||
void auth.fetch().catch(() => {
|
void auth.fetchAuth().catch(() => {
|
||||||
history.replace(homePath());
|
history.replace(homePath());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import ImageInput from "./components/ImageInput";
|
|||||||
import SettingRow from "./components/SettingRow";
|
import SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
function Details() {
|
function Details() {
|
||||||
const { auth, dialogs, ui } = useStores();
|
const { dialogs, ui } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -65,7 +65,7 @@ function Details() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await auth.updateTeam({
|
await team.save({
|
||||||
name,
|
name,
|
||||||
subdomain,
|
subdomain,
|
||||||
defaultCollectionId,
|
defaultCollectionId,
|
||||||
@@ -80,16 +80,7 @@ function Details() {
|
|||||||
toast.error(err.message);
|
toast.error(err.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[team, name, subdomain, defaultCollectionId, publicBranding, customTheme, t]
|
||||||
auth,
|
|
||||||
name,
|
|
||||||
subdomain,
|
|
||||||
defaultCollectionId,
|
|
||||||
team.preferences,
|
|
||||||
publicBranding,
|
|
||||||
customTheme,
|
|
||||||
t,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNameChange = React.useCallback(
|
const handleNameChange = React.useCallback(
|
||||||
@@ -107,9 +98,7 @@ function Details() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleAvatarUpload = async (avatarUrl: string) => {
|
const handleAvatarUpload = async (avatarUrl: string) => {
|
||||||
await auth.updateTeam({
|
await team.save({ avatarUrl });
|
||||||
avatarUrl,
|
|
||||||
});
|
|
||||||
toast.success(t("Logo updated"));
|
toast.success(t("Logo updated"));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -288,8 +277,8 @@ function Details() {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<Button type="submit" disabled={auth.isSaving || !isValid}>
|
<Button type="submit" disabled={team.isSaving || !isValid}>
|
||||||
{auth.isSaving ? `${t("Saving")}…` : t("Save")}
|
{team.isSaving ? `${t("Saving")}…` : t("Save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{can.delete && (
|
{can.delete && (
|
||||||
|
|||||||
@@ -9,23 +9,20 @@ import Scene from "~/components/Scene";
|
|||||||
import Switch from "~/components/Switch";
|
import Switch from "~/components/Switch";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
import useStores from "~/hooks/useStores";
|
|
||||||
import SettingRow from "./components/SettingRow";
|
import SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
function Features() {
|
function Features() {
|
||||||
const { auth } = useStores();
|
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handlePreferenceChange =
|
const handlePreferenceChange =
|
||||||
(inverted = false) =>
|
(inverted = false) =>
|
||||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const preferences = {
|
team.setPreference(
|
||||||
...team.preferences,
|
ev.target.name as TeamPreference,
|
||||||
[ev.target.name]: inverted ? !ev.target.checked : ev.target.checked,
|
inverted ? !ev.target.checked : ev.target.checked
|
||||||
};
|
);
|
||||||
|
await team.save();
|
||||||
await auth.updateTeam({ preferences });
|
|
||||||
toast.success(t("Settings saved"));
|
toast.success(t("Settings saved"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,24 +19,23 @@ import SettingRow from "./components/SettingRow";
|
|||||||
|
|
||||||
function Preferences() {
|
function Preferences() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dialogs, auth } = useStores();
|
const { dialogs } = useStores();
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
|
|
||||||
const handlePreferenceChange =
|
const handlePreferenceChange =
|
||||||
(inverted = false) =>
|
(inverted = false) =>
|
||||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const preferences = {
|
user.setPreference(
|
||||||
...user.preferences,
|
ev.target.name as UserPreference,
|
||||||
[ev.target.name]: inverted ? !ev.target.checked : ev.target.checked,
|
inverted ? !ev.target.checked : ev.target.checked
|
||||||
};
|
);
|
||||||
|
await user.save();
|
||||||
await auth.updateUser({ preferences });
|
|
||||||
toast.success(t("Preferences saved"));
|
toast.success(t("Preferences saved"));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLanguageChange = async (language: string) => {
|
const handleLanguageChange = async (language: string) => {
|
||||||
await auth.updateUser({ language });
|
await user.save({ language });
|
||||||
toast.success(t("Preferences saved"));
|
toast.success(t("Preferences saved"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import Input from "~/components/Input";
|
|||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||||
import useStores from "~/hooks/useStores";
|
|
||||||
import ImageInput from "./components/ImageInput";
|
import ImageInput from "./components/ImageInput";
|
||||||
import SettingRow from "./components/SettingRow";
|
import SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { auth } = useStores();
|
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const form = React.useRef<HTMLFormElement>(null);
|
const form = React.useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = React.useState<string>(user.name || "");
|
const [name, setName] = React.useState<string>(user.name || "");
|
||||||
@@ -24,9 +22,7 @@ const Profile = () => {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await auth.updateUser({
|
await user.save({ name });
|
||||||
name,
|
|
||||||
});
|
|
||||||
toast.success(t("Profile saved"));
|
toast.success(t("Profile saved"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message);
|
toast.error(err.message);
|
||||||
@@ -38,9 +34,7 @@ const Profile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAvatarUpload = async (avatarUrl: string) => {
|
const handleAvatarUpload = async (avatarUrl: string) => {
|
||||||
await auth.updateUser({
|
await user.save({ avatarUrl });
|
||||||
avatarUrl,
|
|
||||||
});
|
|
||||||
toast.success(t("Profile picture updated"));
|
toast.success(t("Profile picture updated"));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,7 +43,7 @@ const Profile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isValid = form.current?.checkValidity();
|
const isValid = form.current?.checkValidity();
|
||||||
const { isSaving } = auth;
|
const { isSaving } = user;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scene title={t("Profile")} icon={<ProfileIcon />}>
|
<Scene title={t("Profile")} icon={<ProfileIcon />}>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import DomainManagement from "./components/DomainManagement";
|
|||||||
import SettingRow from "./components/SettingRow";
|
import SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
function Security() {
|
function Security() {
|
||||||
const { auth, authenticationProviders, dialogs } = useStores();
|
const { authenticationProviders, dialogs } = useStores();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -61,13 +61,13 @@ function Security() {
|
|||||||
async (newData) => {
|
async (newData) => {
|
||||||
try {
|
try {
|
||||||
setData(newData);
|
setData(newData);
|
||||||
await auth.updateTeam(newData);
|
await team.save(newData);
|
||||||
showSuccessMessage();
|
showSuccessMessage();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message);
|
toast.error(err.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[auth, showSuccessMessage]
|
[team, showSuccessMessage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import Input from "~/components/Input";
|
|||||||
import NudeButton from "~/components/NudeButton";
|
import NudeButton from "~/components/NudeButton";
|
||||||
import Tooltip from "~/components/Tooltip";
|
import Tooltip from "~/components/Tooltip";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
import useStores from "~/hooks/useStores";
|
|
||||||
import SettingRow from "./SettingRow";
|
import SettingRow from "./SettingRow";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -19,7 +18,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function DomainManagement({ onSuccess }: Props) {
|
function DomainManagement({ onSuccess }: Props) {
|
||||||
const { auth } = useStores();
|
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -35,16 +33,14 @@ function DomainManagement({ onSuccess }: Props) {
|
|||||||
|
|
||||||
const handleSaveDomains = React.useCallback(async () => {
|
const handleSaveDomains = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await auth.updateTeam({
|
await team.save({ allowedDomains });
|
||||||
allowedDomains,
|
|
||||||
});
|
|
||||||
onSuccess();
|
onSuccess();
|
||||||
setExistingDomainsTouched(false);
|
setExistingDomainsTouched(false);
|
||||||
updateLastKnownDomainCount(allowedDomains.length);
|
updateLastKnownDomainCount(allowedDomains.length);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message);
|
toast.error(err.message);
|
||||||
}
|
}
|
||||||
}, [auth, allowedDomains, onSuccess]);
|
}, [team, allowedDomains, onSuccess]);
|
||||||
|
|
||||||
const handleRemoveDomain = async (index: number) => {
|
const handleRemoveDomain = async (index: number) => {
|
||||||
const newDomains = allowedDomains.filter((_, i) => index !== i);
|
const newDomains = allowedDomains.filter((_, i) => index !== i);
|
||||||
@@ -132,7 +128,7 @@ function DomainManagement({ onSuccess }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSaveDomains}
|
onClick={handleSaveDomains}
|
||||||
disabled={auth.isSaving}
|
disabled={team.isSaving}
|
||||||
>
|
>
|
||||||
<Trans>Save changes</Trans>
|
<Trans>Save changes</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as Sentry from "@sentry/react";
|
|||||||
import invariant from "invariant";
|
import invariant from "invariant";
|
||||||
import { observable, action, computed, autorun, runInAction } from "mobx";
|
import { observable, action, computed, autorun, runInAction } from "mobx";
|
||||||
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
||||||
import { CustomTheme, TeamPreferences, UserPreferences } from "@shared/types";
|
import { CustomTheme } from "@shared/types";
|
||||||
import Storage from "@shared/utils/Storage";
|
import Storage from "@shared/utils/Storage";
|
||||||
import { getCookieDomain, parseDomain } from "@shared/utils/domains";
|
import { getCookieDomain, parseDomain } from "@shared/utils/domains";
|
||||||
import RootStore from "~/stores/RootStore";
|
import RootStore from "~/stores/RootStore";
|
||||||
@@ -10,17 +10,19 @@ import Policy from "~/models/Policy";
|
|||||||
import Team from "~/models/Team";
|
import Team from "~/models/Team";
|
||||||
import User from "~/models/User";
|
import User from "~/models/User";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
|
import { PartialWithId } from "~/types";
|
||||||
import { client } from "~/utils/ApiClient";
|
import { client } from "~/utils/ApiClient";
|
||||||
import Desktop from "~/utils/Desktop";
|
import Desktop from "~/utils/Desktop";
|
||||||
import Logger from "~/utils/Logger";
|
import Logger from "~/utils/Logger";
|
||||||
import isCloudHosted from "~/utils/isCloudHosted";
|
import isCloudHosted from "~/utils/isCloudHosted";
|
||||||
|
import Store from "./base/Store";
|
||||||
|
|
||||||
const AUTH_STORE = "AUTH_STORE";
|
const AUTH_STORE = "AUTH_STORE";
|
||||||
const NO_REDIRECT_PATHS = ["/", "/create", "/home", "/logout"];
|
const NO_REDIRECT_PATHS = ["/", "/create", "/home", "/logout"];
|
||||||
|
|
||||||
type PersistedData = {
|
type PersistedData = {
|
||||||
user?: User;
|
user?: PartialWithId<User>;
|
||||||
team?: Team;
|
team?: PartialWithId<Team>;
|
||||||
collaborationToken?: string;
|
collaborationToken?: string;
|
||||||
availableTeams?: {
|
availableTeams?: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -46,14 +48,14 @@ export type Config = {
|
|||||||
providers: Provider[];
|
providers: Provider[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class AuthStore {
|
export default class AuthStore extends Store<Team> {
|
||||||
/* The user that is currently signed in. */
|
/* The ID of the user that is currently signed in. */
|
||||||
@observable
|
@observable
|
||||||
user?: User | null;
|
currentUserId?: string | null;
|
||||||
|
|
||||||
/* The team that the current user is signed into. */
|
/* The ID of the team that is currently signed in. */
|
||||||
@observable
|
@observable
|
||||||
team?: Team | null;
|
currentTeamId?: string | null;
|
||||||
|
|
||||||
/* A short-lived token to be used to authenticate with the collaboration server. */
|
/* A short-lived token to be used to authenticate with the collaboration server. */
|
||||||
@observable
|
@observable
|
||||||
@@ -69,21 +71,10 @@ export default class AuthStore {
|
|||||||
isSignedIn: boolean;
|
isSignedIn: boolean;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
/* A list of cancan policies for the current user. */
|
|
||||||
@observable
|
|
||||||
policies: Policy[] = [];
|
|
||||||
|
|
||||||
/* The authentication provider the user signed in with. */
|
/* The authentication provider the user signed in with. */
|
||||||
@observable
|
@observable
|
||||||
lastSignedIn?: string | null;
|
lastSignedIn?: string | null;
|
||||||
|
|
||||||
/* Whether the user is currently saving their profile or team settings. */
|
|
||||||
@observable
|
|
||||||
isSaving = false;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
isFetching = true;
|
|
||||||
|
|
||||||
/* Whether the user is currently suspended. */
|
/* Whether the user is currently suspended. */
|
||||||
@observable
|
@observable
|
||||||
isSuspended = false;
|
isSuspended = false;
|
||||||
@@ -99,12 +90,14 @@ export default class AuthStore {
|
|||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
|
|
||||||
constructor(rootStore: RootStore) {
|
constructor(rootStore: RootStore) {
|
||||||
|
super(rootStore, Team);
|
||||||
|
|
||||||
this.rootStore = rootStore;
|
this.rootStore = rootStore;
|
||||||
// attempt to load the previous state of this store from localstorage
|
// attempt to load the previous state of this store from localstorage
|
||||||
const data: PersistedData = Storage.get(AUTH_STORE) || {};
|
const data: PersistedData = Storage.get(AUTH_STORE) || {};
|
||||||
|
|
||||||
this.rehydrate(data);
|
this.rehydrate(data);
|
||||||
void this.fetch();
|
void this.fetchAuth();
|
||||||
|
|
||||||
// persists this entire store to localstorage whenever any keys are changed
|
// persists this entire store to localstorage whenever any keys are changed
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
@@ -138,21 +131,44 @@ export default class AuthStore {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
rehydrate(data: PersistedData) {
|
rehydrate(data: PersistedData) {
|
||||||
this.user = data.user ? new User(data.user, this as any) : undefined;
|
if (data.policies) {
|
||||||
this.team = data.team ? new Team(data.team, this as any) : undefined;
|
this.addPolicies(data.policies);
|
||||||
|
}
|
||||||
|
if (data.team) {
|
||||||
|
this.add(data.team);
|
||||||
|
}
|
||||||
|
if (data.user) {
|
||||||
|
this.rootStore.users.add(data.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentUserId = data.user?.id;
|
||||||
this.collaborationToken = data.collaborationToken;
|
this.collaborationToken = data.collaborationToken;
|
||||||
this.lastSignedIn = getCookie("lastSignedIn");
|
this.lastSignedIn = getCookie("lastSignedIn");
|
||||||
this.addPolicies(data.policies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addPolicies(policies?: Policy[]) {
|
/** The current user */
|
||||||
if (policies) {
|
@computed
|
||||||
// cache policies in this store so that they are persisted between sessions
|
get user() {
|
||||||
this.policies = policies;
|
return this.currentUserId
|
||||||
policies.forEach((policy) => this.rootStore.policies.add(policy));
|
? this.rootStore.users.get(this.currentUserId)
|
||||||
}
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The current team */
|
||||||
|
@computed
|
||||||
|
get team() {
|
||||||
|
return this.orderedData.at(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The current team's policies */
|
||||||
|
@computed
|
||||||
|
get policies() {
|
||||||
|
return this.currentTeamId
|
||||||
|
? [this.rootStore.policies.get(this.currentTeamId)]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the user is signed in */
|
||||||
@computed
|
@computed
|
||||||
get authenticated(): boolean {
|
get authenticated(): boolean {
|
||||||
return !!this.user && !!this.team;
|
return !!this.user && !!this.team;
|
||||||
@@ -177,7 +193,7 @@ export default class AuthStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetch = async () => {
|
fetchAuth = async () => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -185,21 +201,23 @@ export default class AuthStore {
|
|||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
});
|
});
|
||||||
invariant(res?.data, "Auth not available");
|
invariant(res?.data, "Auth not available");
|
||||||
runInAction("AuthStore#fetch", () => {
|
|
||||||
|
runInAction("AuthStore#refresh", () => {
|
||||||
|
const { data } = res;
|
||||||
this.addPolicies(res.policies);
|
this.addPolicies(res.policies);
|
||||||
const { user, team } = res.data;
|
this.add(data.team);
|
||||||
this.user = new User(user, this as any);
|
this.rootStore.users.add(data.user);
|
||||||
this.team = new Team(team, this as any);
|
this.currentUserId = data.user.id;
|
||||||
|
this.currentTeamId = data.team.id;
|
||||||
|
|
||||||
this.availableTeams = res.data.availableTeams;
|
this.availableTeams = res.data.availableTeams;
|
||||||
this.collaborationToken = res.data.collaborationToken;
|
this.collaborationToken = res.data.collaborationToken;
|
||||||
|
|
||||||
if (env.SENTRY_DSN) {
|
if (env.SENTRY_DSN) {
|
||||||
Sentry.configureScope(function (scope) {
|
Sentry.configureScope(function (scope) {
|
||||||
scope.setUser({
|
scope.setUser({ id: this.currentUserId });
|
||||||
id: user.id,
|
scope.setExtra("team", this.team.name);
|
||||||
});
|
scope.setExtra("teamId", this.team.id);
|
||||||
scope.setExtra("team", team.name);
|
|
||||||
scope.setExtra("teamId", team.id);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,16 +225,16 @@ export default class AuthStore {
|
|||||||
// Occurs when the (sub)domain is changed in admin and the user hits an old url
|
// Occurs when the (sub)domain is changed in admin and the user hits an old url
|
||||||
const { hostname, pathname } = window.location;
|
const { hostname, pathname } = window.location;
|
||||||
|
|
||||||
if (this.team.domain) {
|
if (data.team.domain) {
|
||||||
if (this.team.domain !== hostname) {
|
if (data.team.domain !== hostname) {
|
||||||
window.location.href = `${team.url}${pathname}`;
|
window.location.href = `${data.team.url}${pathname}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
isCloudHosted &&
|
isCloudHosted &&
|
||||||
parseDomain(hostname).teamSubdomain !== (team.subdomain ?? "")
|
parseDomain(hostname).teamSubdomain !== (data.team.subdomain ?? "")
|
||||||
) {
|
) {
|
||||||
window.location.href = `${team.url}${pathname}`;
|
window.location.href = `${data.team.url}${pathname}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,79 +268,28 @@ export default class AuthStore {
|
|||||||
deleteUser = async (data: { code: string }) => {
|
deleteUser = async (data: { code: string }) => {
|
||||||
await client.post(`/users.delete`, data);
|
await client.post(`/users.delete`, data);
|
||||||
runInAction("AuthStore#deleteUser", () => {
|
runInAction("AuthStore#deleteUser", () => {
|
||||||
this.user = null;
|
this.currentUserId = null;
|
||||||
this.team = null;
|
this.currentTeamId = null;
|
||||||
this.collaborationToken = null;
|
this.collaborationToken = null;
|
||||||
this.availableTeams = this.availableTeams?.filter(
|
this.availableTeams = this.availableTeams?.filter(
|
||||||
(team) => team.id !== this.team?.id
|
(team) => team.id !== this.team?.id
|
||||||
);
|
);
|
||||||
this.policies = [];
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
deleteTeam = async (data: { code: string }) => {
|
deleteTeam = async (data: { code: string }) => {
|
||||||
await client.post(`/teams.delete`, data);
|
await client.post(`/teams.delete`, data);
|
||||||
|
|
||||||
runInAction("AuthStore#deleteTeam", () => {
|
runInAction("AuthStore#deleteTeam", () => {
|
||||||
this.user = null;
|
this.currentUserId = null;
|
||||||
|
this.currentTeamId = null;
|
||||||
this.availableTeams = this.availableTeams?.filter(
|
this.availableTeams = this.availableTeams?.filter(
|
||||||
(team) => team.id !== this.team?.id
|
(team) => team.id !== this.team?.id
|
||||||
);
|
);
|
||||||
this.policies = [];
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
|
||||||
updateUser = async (params: {
|
|
||||||
name?: string;
|
|
||||||
avatarUrl?: string | null;
|
|
||||||
language?: string;
|
|
||||||
preferences?: UserPreferences;
|
|
||||||
}) => {
|
|
||||||
this.isSaving = true;
|
|
||||||
const previousData = this.user?.toAPI();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.user?.updateData(params);
|
|
||||||
const res = await client.post(`/users.update`, params);
|
|
||||||
invariant(res?.data, "User response not available");
|
|
||||||
this.user?.updateData(res.data);
|
|
||||||
this.addPolicies(res.policies);
|
|
||||||
} catch (err) {
|
|
||||||
this.user?.updateData(previousData);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
this.isSaving = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@action
|
|
||||||
updateTeam = async (params: {
|
|
||||||
name?: string;
|
|
||||||
avatarUrl?: string | null | undefined;
|
|
||||||
sharing?: boolean;
|
|
||||||
defaultCollectionId?: string | null;
|
|
||||||
subdomain?: string | null | undefined;
|
|
||||||
allowedDomains?: string[] | null | undefined;
|
|
||||||
preferences?: TeamPreferences;
|
|
||||||
}) => {
|
|
||||||
this.isSaving = true;
|
|
||||||
const previousData = this.team?.toAPI();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.team?.updateData(params);
|
|
||||||
const res = await client.post(`/team.update`, params);
|
|
||||||
invariant(res?.data, "Team response not available");
|
|
||||||
this.team?.updateData(res.data);
|
|
||||||
this.addPolicies(res.policies);
|
|
||||||
} catch (err) {
|
|
||||||
this.team?.updateData(previousData);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
this.isSaving = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
createTeam = async (params: { name: string }) => {
|
createTeam = async (params: { name: string }) => {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
@@ -378,10 +345,9 @@ export default class AuthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clear all credentials from cache (and local storage via autorun)
|
// clear all credentials from cache (and local storage via autorun)
|
||||||
this.user = null;
|
this.currentUserId = null;
|
||||||
this.team = null;
|
this.currentTeamId = null;
|
||||||
this.collaborationToken = null;
|
this.collaborationToken = null;
|
||||||
this.policies = [];
|
|
||||||
|
|
||||||
// Tell the host application we logged out, if any – allows window cleanup.
|
// Tell the host application we logged out, if any – allows window cleanup.
|
||||||
void Desktop.bridge?.onLogout?.();
|
void Desktop.bridge?.onLogout?.();
|
||||||
|
|||||||
@@ -56,11 +56,8 @@ export default class RootStore {
|
|||||||
webhookSubscriptions: WebhookSubscriptionsStore;
|
webhookSubscriptions: WebhookSubscriptionsStore;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// PoliciesStore must be initialized before AuthStore
|
|
||||||
this.policies = new PoliciesStore(this);
|
|
||||||
this.apiKeys = new ApiKeysStore(this);
|
this.apiKeys = new ApiKeysStore(this);
|
||||||
this.authenticationProviders = new AuthenticationProvidersStore(this);
|
this.authenticationProviders = new AuthenticationProvidersStore(this);
|
||||||
this.auth = new AuthStore(this);
|
|
||||||
this.collections = new CollectionsStore(this);
|
this.collections = new CollectionsStore(this);
|
||||||
this.collectionGroupMemberships = new CollectionGroupMembershipsStore(this);
|
this.collectionGroupMemberships = new CollectionGroupMembershipsStore(this);
|
||||||
this.comments = new CommentsStore(this);
|
this.comments = new CommentsStore(this);
|
||||||
@@ -73,6 +70,7 @@ export default class RootStore {
|
|||||||
this.memberships = new MembershipsStore(this);
|
this.memberships = new MembershipsStore(this);
|
||||||
this.notifications = new NotificationsStore(this);
|
this.notifications = new NotificationsStore(this);
|
||||||
this.pins = new PinsStore(this);
|
this.pins = new PinsStore(this);
|
||||||
|
this.policies = new PoliciesStore(this);
|
||||||
this.presence = new DocumentPresenceStore();
|
this.presence = new DocumentPresenceStore();
|
||||||
this.revisions = new RevisionsStore(this);
|
this.revisions = new RevisionsStore(this);
|
||||||
this.searches = new SearchesStore(this);
|
this.searches = new SearchesStore(this);
|
||||||
@@ -84,6 +82,9 @@ export default class RootStore {
|
|||||||
this.views = new ViewsStore(this);
|
this.views = new ViewsStore(this);
|
||||||
this.fileOperations = new FileOperationsStore(this);
|
this.fileOperations = new FileOperationsStore(this);
|
||||||
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
|
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
|
||||||
|
|
||||||
|
// AuthStore must be initialized last as it makes use of the other stores.
|
||||||
|
this.auth = new AuthStore(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
|
|||||||
@@ -21,34 +21,46 @@ import * as T from "./schema";
|
|||||||
const router = new Router();
|
const router = new Router();
|
||||||
const emailEnabled = !!(env.SMTP_HOST || env.ENVIRONMENT === "development");
|
const emailEnabled = !!(env.SMTP_HOST || env.ENVIRONMENT === "development");
|
||||||
|
|
||||||
|
const handleTeamUpdate = async (ctx: APIContext<T.TeamsUpdateSchemaReq>) => {
|
||||||
|
const { transaction } = ctx.state;
|
||||||
|
const { user } = ctx.state.auth;
|
||||||
|
const team = await Team.findByPk(user.teamId, {
|
||||||
|
include: [{ model: TeamDomain, separate: true }],
|
||||||
|
lock: transaction.LOCK.UPDATE,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
authorize(user, "update", team);
|
||||||
|
|
||||||
|
const updatedTeam = await teamUpdater({
|
||||||
|
params: ctx.input.body,
|
||||||
|
user,
|
||||||
|
team,
|
||||||
|
ip: ctx.request.ip,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: presentTeam(updatedTeam),
|
||||||
|
policies: presentPolicies(user, [updatedTeam]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"team.update",
|
"team.update",
|
||||||
rateLimiter(RateLimiterStrategy.TenPerHour),
|
rateLimiter(RateLimiterStrategy.TenPerHour),
|
||||||
auth(),
|
auth(),
|
||||||
validate(T.TeamsUpdateSchema),
|
validate(T.TeamsUpdateSchema),
|
||||||
transaction(),
|
transaction(),
|
||||||
async (ctx: APIContext<T.TeamsUpdateSchemaReq>) => {
|
handleTeamUpdate
|
||||||
const { transaction } = ctx.state;
|
);
|
||||||
const { user } = ctx.state.auth;
|
|
||||||
const team = await Team.findByPk(user.teamId, {
|
|
||||||
include: [{ model: TeamDomain }],
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
authorize(user, "update", team);
|
|
||||||
|
|
||||||
const updatedTeam = await teamUpdater({
|
router.post(
|
||||||
params: ctx.input.body,
|
"teams.update",
|
||||||
user,
|
rateLimiter(RateLimiterStrategy.TenPerHour),
|
||||||
team,
|
auth(),
|
||||||
ip: ctx.request.ip,
|
validate(T.TeamsUpdateSchema),
|
||||||
transaction,
|
transaction(),
|
||||||
});
|
handleTeamUpdate
|
||||||
|
|
||||||
ctx.body = {
|
|
||||||
data: presentTeam(updatedTeam),
|
|
||||||
policies: presentPolicies(user, [updatedTeam]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
|
|||||||
Reference in New Issue
Block a user