aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FrontEnd/src/app/App.tsx2
-rw-r--r--FrontEnd/src/app/http/user.ts46
-rw-r--r--FrontEnd/src/app/services/timeline.ts22
-rw-r--r--FrontEnd/src/app/services/user.ts57
-rw-r--r--FrontEnd/src/app/views/admin/Admin.tsx4
-rw-r--r--FrontEnd/src/app/views/admin/UserAdmin.tsx4
-rw-r--r--FrontEnd/src/app/views/common/AppBar.tsx4
-rw-r--r--FrontEnd/src/app/views/home/BoardWithUser.tsx4
8 files changed, 101 insertions, 42 deletions
diff --git a/FrontEnd/src/app/App.tsx b/FrontEnd/src/app/App.tsx
index 01b1883b..6cdf2434 100644
--- a/FrontEnd/src/app/App.tsx
+++ b/FrontEnd/src/app/App.tsx
@@ -58,7 +58,7 @@ const App: React.FC = () => {
<Route path="/users/:username">
<User />
</Route>
- {user && user.administrator && (
+ {user && user.hasAdministrationPermission && (
<Route path="/admin">
<LazyAdmin user={user} />
</Route>
diff --git a/FrontEnd/src/app/http/user.ts b/FrontEnd/src/app/http/user.ts
index a0a02cce..9ba6508f 100644
--- a/FrontEnd/src/app/http/user.ts
+++ b/FrontEnd/src/app/http/user.ts
@@ -12,10 +12,18 @@ import {
convertToNotModified,
} from "./common";
+export const kUserPermissionList = [
+ "UserManagement",
+ "AllTimelineManagement",
+ "HighlightTimelineManagement",
+] as const;
+
+export type UserPermission = typeof kUserPermissionList[number];
+
export interface HttpUser {
uniqueId: string;
username: string;
- administrator: boolean;
+ permissions: UserPermission[];
nickname: string;
}
@@ -54,6 +62,16 @@ export interface IHttpUserClient {
): Promise<BlobWithEtag | NotModified>;
putAvatar(username: string, data: Blob, token: string): Promise<void>;
changePassword(req: HttpChangePasswordRequest, token: string): Promise<void>;
+ putUserPermission(
+ username: string,
+ permission: UserPermission,
+ token: string
+ ): Promise<void>;
+ deleteUserPermission(
+ username: string,
+ permission: UserPermission,
+ token: string
+ ): Promise<void>;
}
export class HttpUserClient implements IHttpUserClient {
@@ -119,6 +137,32 @@ export class HttpUserClient implements IHttpUserClient {
.catch(convertToNetworkError)
.then();
}
+
+ putUserPermission(
+ username: string,
+ permission: UserPermission,
+ token: string
+ ): Promise<void> {
+ return axios
+ .put(
+ `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}`
+ )
+ .catch(convertToNetworkError)
+ .then();
+ }
+
+ deleteUserPermission(
+ username: string,
+ permission: UserPermission,
+ token: string
+ ): Promise<void> {
+ return axios
+ .delete(
+ `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}`
+ )
+ .catch(convertToNetworkError)
+ .then();
+ }
}
let client: IHttpUserClient = new HttpUserClient();
diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts
index 2cbbffab..c58516fc 100644
--- a/FrontEnd/src/app/services/timeline.ts
+++ b/FrontEnd/src/app/services/timeline.ts
@@ -29,11 +29,11 @@ export type { TimelineVisibility } from "@/http/timeline";
import { dataStorage, throwIfNotNetworkError, BlobOrStatus } from "./common";
import { DataHub, WithSyncStatus } from "./DataHub";
import {
- UserAuthInfo,
checkLogin,
userService,
userInfoService,
User,
+ AuthUser,
} from "./user";
export type TimelineInfo = HttpTimelineInfo;
@@ -608,10 +608,11 @@ export class TimelineService {
}
hasReadPermission(
- user: UserAuthInfo | null | undefined,
+ user: AuthUser | null | undefined,
timeline: TimelineInfo
): boolean {
- if (user != null && user.administrator) return true;
+ if (user != null && user.hasAllTimelineAdministrationPermission)
+ return true;
const { visibility } = timeline;
if (visibility === "Public") {
@@ -631,10 +632,11 @@ export class TimelineService {
}
hasPostPermission(
- user: UserAuthInfo | null | undefined,
+ user: AuthUser | null | undefined,
timeline: TimelineInfo
): boolean {
- if (user != null && user.administrator) return true;
+ if (user != null && user.hasAllTimelineAdministrationPermission)
+ return true;
return (
user != null &&
@@ -644,20 +646,22 @@ export class TimelineService {
}
hasManagePermission(
- user: UserAuthInfo | null | undefined,
+ user: AuthUser | null | undefined,
timeline: TimelineInfo
): boolean {
- if (user != null && user.administrator) return true;
+ if (user != null && user.hasAllTimelineAdministrationPermission)
+ return true;
return user != null && user.username == timeline.owner.username;
}
hasModifyPostPermission(
- user: UserAuthInfo | null | undefined,
+ user: AuthUser | null | undefined,
timeline: TimelineInfo,
post: TimelinePostInfo
): boolean {
- if (user != null && user.administrator) return true;
+ if (user != null && user.hasAllTimelineAdministrationPermission)
+ return true;
return (
user != null &&
diff --git a/FrontEnd/src/app/services/user.ts b/FrontEnd/src/app/services/user.ts
index cd6d1c15..0166bce0 100644
--- a/FrontEnd/src/app/services/user.ts
+++ b/FrontEnd/src/app/services/user.ts
@@ -14,6 +14,7 @@ import {
getHttpUserClient,
HttpUserNotExistError,
HttpUser,
+ UserPermission,
} from "@/http/user";
import { dataStorage, throwIfNotNetworkError } from "./common";
@@ -22,13 +23,26 @@ import { pushAlert } from "./alert";
export type User = HttpUser;
-export interface UserAuthInfo {
+export class AuthUser implements User {
+ constructor(user: User, public token: string) {
+ this.uniqueId = user.uniqueId;
+ this.username = user.username;
+ this.permissions = user.permissions;
+ this.nickname = user.nickname;
+ }
+
+ uniqueId: string;
username: string;
- administrator: boolean;
-}
+ permissions: UserPermission[];
+ nickname: string;
-export interface UserWithToken extends User {
- token: string;
+ get hasAdministrationPermission(): boolean {
+ return this.permissions.length !== 0;
+ }
+
+ get hasAllTimelineAdministrationPermission(): boolean {
+ return this.permissions.includes("AllTimelineManagement");
+ }
}
export interface LoginCredentials {
@@ -43,24 +57,24 @@ export class BadCredentialError {
const USER_STORAGE_KEY = "currentuser";
export class UserService {
- private userSubject = new BehaviorSubject<UserWithToken | null | undefined>(
+ private userSubject = new BehaviorSubject<AuthUser | null | undefined>(
undefined
);
- get user$(): Observable<UserWithToken | null | undefined> {
+ get user$(): Observable<AuthUser | null | undefined> {
return this.userSubject;
}
- get currentUser(): UserWithToken | null | undefined {
+ get currentUser(): AuthUser | null | undefined {
return this.userSubject.value;
}
- async checkLoginState(): Promise<UserWithToken | null> {
+ async checkLoginState(): Promise<AuthUser | null> {
if (this.currentUser !== undefined) {
console.warn("Already checked user. Can't check twice.");
}
- const savedUser = await dataStorage.getItem<UserWithToken | null>(
+ const savedUser = await dataStorage.getItem<AuthUser | null>(
USER_STORAGE_KEY
);
@@ -74,8 +88,8 @@ export class UserService {
const savedToken = savedUser.token;
try {
const res = await getHttpTokenClient().verify({ token: savedToken });
- const user: UserWithToken = { ...res.user, token: savedToken };
- await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user);
+ const user = new AuthUser(res.user, savedToken);
+ await dataStorage.setItem<AuthUser>(USER_STORAGE_KEY, user);
this.userSubject.next(user);
pushAlert({
type: "success",
@@ -116,12 +130,9 @@ export class UserService {
...credentials,
expire: 30,
});
- const user: UserWithToken = {
- ...res.user,
- token: res.token,
- };
+ const user = new AuthUser(res.user, res.token);
if (rememberMe) {
- await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user);
+ await dataStorage.setItem<AuthUser>(USER_STORAGE_KEY, user);
}
this.userSubject.next(user);
} catch (e) {
@@ -169,8 +180,8 @@ export class UserService {
export const userService = new UserService();
-export function useRawUser(): UserWithToken | null | undefined {
- const [user, setUser] = useState<UserWithToken | null | undefined>(
+export function useRawUser(): AuthUser | null | undefined {
+ const [user, setUser] = useState<AuthUser | null | undefined>(
userService.currentUser
);
useEffect(() => {
@@ -182,8 +193,8 @@ export function useRawUser(): UserWithToken | null | undefined {
return user;
}
-export function useUser(): UserWithToken | null {
- const [user, setUser] = useState<UserWithToken | null>(() => {
+export function useUser(): AuthUser | null {
+ const [user, setUser] = useState<AuthUser | null>(() => {
const initUser = userService.currentUser;
if (initUser === undefined) {
throw new UiLogicError(
@@ -208,7 +219,7 @@ export function useUser(): UserWithToken | null {
return user;
}
-export function useUserLoggedIn(): UserWithToken {
+export function useUserLoggedIn(): AuthUser {
const user = useUser();
if (user == null) {
throw new UiLogicError("You assert user has logged in but actually not.");
@@ -216,7 +227,7 @@ export function useUserLoggedIn(): UserWithToken {
return user;
}
-export function checkLogin(): UserWithToken {
+export function checkLogin(): AuthUser {
const user = userService.currentUser;
if (user == null) {
throw new UiLogicError("You must login to perform the operation.");
diff --git a/FrontEnd/src/app/views/admin/Admin.tsx b/FrontEnd/src/app/views/admin/Admin.tsx
index 9c0250e7..a64a9bc0 100644
--- a/FrontEnd/src/app/views/admin/Admin.tsx
+++ b/FrontEnd/src/app/views/admin/Admin.tsx
@@ -8,12 +8,12 @@ import {
} from "react-router";
import { Nav } from "react-bootstrap";
-import { UserWithToken } from "@/services/user";
+import { AuthUser } from "@/services/user";
import UserAdmin from "./UserAdmin";
interface AdminProps {
- user: UserWithToken;
+ user: AuthUser;
}
const Admin: React.FC<AdminProps> = (props) => {
diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx
index 0f5f8796..4ad9ed09 100644
--- a/FrontEnd/src/app/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx
@@ -10,7 +10,7 @@ import {
} from "react-bootstrap";
import OperationDialog from "../common/OperationDialog";
-import { User, UserWithToken } from "@/services/user";
+import { User, AuthUser } from "@/services/user";
const apiBaseUrl = "/api";
@@ -285,7 +285,7 @@ const UserChangePermissionDialog: React.FC<UserChangePermissionDialogProps> = (
};
interface UserAdminProps {
- user: UserWithToken;
+ user: AuthUser;
}
const UserAdmin: React.FC<UserAdminProps> = (props) => {
diff --git a/FrontEnd/src/app/views/common/AppBar.tsx b/FrontEnd/src/app/views/common/AppBar.tsx
index 8f35b482..11d3de04 100644
--- a/FrontEnd/src/app/views/common/AppBar.tsx
+++ b/FrontEnd/src/app/views/common/AppBar.tsx
@@ -15,7 +15,7 @@ const AppBar: React.FC = (_) => {
const { t } = useTranslation();
- const isAdministrator = user && user.administrator;
+ const hasAdministrationPermission = user && user.hasAdministrationPermission;
const [expand, setExpand] = React.useState<boolean>(false);
const collapse = (): void => setExpand(false);
@@ -56,7 +56,7 @@ const AppBar: React.FC = (_) => {
{t("nav.about")}
</NavLink>
- {isAdministrator && (
+ {hasAdministrationPermission && (
<NavLink
to="/admin"
className="nav-link"
diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx
index fbe1dd89..bbef835a 100644
--- a/FrontEnd/src/app/views/home/BoardWithUser.tsx
+++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx
@@ -2,14 +2,14 @@ import React from "react";
import { Row, Col } from "react-bootstrap";
import { useTranslation } from "react-i18next";
-import { UserWithToken } from "@/services/user";
+import { AuthUser } from "@/services/user";
import { TimelineInfo } from "@/services/timeline";
import { getHttpTimelineClient } from "@/http/timeline";
import TimelineBoard from "./TimelineBoard";
import OfflineBoard from "./OfflineBoard";
-const BoardWithUser: React.FC<{ user: UserWithToken }> = ({ user }) => {
+const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => {
const { t } = useTranslation();
const [ownTimelines, setOwnTimelines] = React.useState<