From a687a23a641ca262dbffd383af129a0069fbc5ee Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 16 Nov 2020 16:30:32 +0800 Subject: ... --- FrontEnd/src/app/App.tsx | 2 +- FrontEnd/src/app/http/user.ts | 46 ++++++++++++++++++++- FrontEnd/src/app/services/timeline.ts | 22 ++++++----- FrontEnd/src/app/services/user.ts | 57 ++++++++++++++++----------- FrontEnd/src/app/views/admin/Admin.tsx | 4 +- FrontEnd/src/app/views/admin/UserAdmin.tsx | 4 +- FrontEnd/src/app/views/common/AppBar.tsx | 4 +- FrontEnd/src/app/views/home/BoardWithUser.tsx | 4 +- 8 files changed, 101 insertions(+), 42 deletions(-) (limited to 'FrontEnd/src') 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 = () => { - {user && user.administrator && ( + {user && user.hasAdministrationPermission && ( 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; putAvatar(username: string, data: Blob, token: string): Promise; changePassword(req: HttpChangePasswordRequest, token: string): Promise; + putUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise; + deleteUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise; } 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 { + return axios + .put( + `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}` + ) + .catch(convertToNetworkError) + .then(); + } + + deleteUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise { + 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( + private userSubject = new BehaviorSubject( undefined ); - get user$(): Observable { + get user$(): Observable { return this.userSubject; } - get currentUser(): UserWithToken | null | undefined { + get currentUser(): AuthUser | null | undefined { return this.userSubject.value; } - async checkLoginState(): Promise { + async checkLoginState(): Promise { if (this.currentUser !== undefined) { console.warn("Already checked user. Can't check twice."); } - const savedUser = await dataStorage.getItem( + const savedUser = await dataStorage.getItem( 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(USER_STORAGE_KEY, user); + const user = new AuthUser(res.user, savedToken); + await dataStorage.setItem(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(USER_STORAGE_KEY, user); + await dataStorage.setItem(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( +export function useRawUser(): AuthUser | null | undefined { + const [user, setUser] = useState( userService.currentUser ); useEffect(() => { @@ -182,8 +193,8 @@ export function useRawUser(): UserWithToken | null | undefined { return user; } -export function useUser(): UserWithToken | null { - const [user, setUser] = useState(() => { +export function useUser(): AuthUser | null { + const [user, setUser] = useState(() => { 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 = (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 = ( }; interface UserAdminProps { - user: UserWithToken; + user: AuthUser; } const UserAdmin: React.FC = (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(false); const collapse = (): void => setExpand(false); @@ -56,7 +56,7 @@ const AppBar: React.FC = (_) => { {t("nav.about")} - {isAdministrator && ( + {hasAdministrationPermission && ( = ({ user }) => { +const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { const { t } = useTranslation(); const [ownTimelines, setOwnTimelines] = React.useState< -- cgit v1.2.3