From 4ddada1fe3d00c5912995f2b1043c55ecb8000c0 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 7 Dec 2025 17:21:46 -0800 Subject: [PATCH] add isAdmin to auth state --- .../api/jellyfin/jellyfin-controller.ts | 1 + .../api/navidrome/navidrome-controller.ts | 1 + src/renderer/api/subsonic/subsonic-api.ts | 2 +- .../api/subsonic/subsonic-controller.ts | 4 +++- .../features/login/routes/login-route.tsx | 1 + .../servers/components/add-server-form.tsx | 1 + .../servers/components/edit-server-form.tsx | 2 ++ src/renderer/store/auth.store.ts | 20 +++++++++++++++++++ src/shared/api/subsonic/subsonic-types.ts | 20 ++++++++++++++++++- src/shared/types/domain-types.ts | 2 ++ 10 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index 91857dd68..c15f261c1 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -91,6 +91,7 @@ export const JellyfinController: InternalControllerEndpoint = { return { credential: res.body.AccessToken, + isAdmin: Boolean(res.body.User.Policy.IsAdministrator), userId: res.body.User.Id, username: res.body.User.Name, }; diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 45959fa06..6979edbf7 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -131,6 +131,7 @@ export const NavidromeController: InternalControllerEndpoint = { return { credential: `u=${body.username}&s=${res.body.data.subsonicSalt}&t=${res.body.data.subsonicToken}`, + isAdmin: Boolean(res.body.data.isAdmin), ndCredential: res.body.data.token, userId: res.body.data.id, username: res.body.data.username, diff --git a/src/renderer/api/subsonic/subsonic-api.ts b/src/renderer/api/subsonic/subsonic-api.ts index 587bdbea5..37f7c1ff0 100644 --- a/src/renderer/api/subsonic/subsonic-api.ts +++ b/src/renderer/api/subsonic/subsonic-api.ts @@ -16,7 +16,7 @@ const c = initContract(); export const contract = c.router({ authenticate: { method: 'GET', - path: 'ping.view', + path: 'getUser.view', query: ssType._parameters.authenticate, responses: { 200: ssType._response.authenticate, diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index 0538071b7..fa260806f 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -132,6 +132,7 @@ export const SubsonicController: InternalControllerEndpoint = { query: { c: 'Feishin', f: 'json', + username: body.username, v: '1.13.0', ...credentialParams, }, @@ -143,7 +144,8 @@ export const SubsonicController: InternalControllerEndpoint = { return { credential, - userId: null, + isAdmin: resp.body.user.adminRoles, + userId: resp.body.user.username, username: body.username, }; }, diff --git a/src/renderer/features/login/routes/login-route.tsx b/src/renderer/features/login/routes/login-route.tsx index 9e949f732..4f133f70b 100644 --- a/src/renderer/features/login/routes/login-route.tsx +++ b/src/renderer/features/login/routes/login-route.tsx @@ -140,6 +140,7 @@ const LoginRoute = () => { const serverItem: ServerListItemWithCredential = { credential: data.credential, id: nanoid(), + isAdmin: data.isAdmin, name: serverName, type: serverType as ServerType, url: serverUrl.replace(/\/$/, ''), diff --git a/src/renderer/features/servers/components/add-server-form.tsx b/src/renderer/features/servers/components/add-server-form.tsx index 30427aebf..2e7579d5b 100644 --- a/src/renderer/features/servers/components/add-server-form.tsx +++ b/src/renderer/features/servers/components/add-server-form.tsx @@ -156,6 +156,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => { const serverItem: ServerListItemWithCredential = { credential: data.credential, id: nanoid(), + isAdmin: data.isAdmin, name: values.name, type: values.type as ServerType, url: values.url.replace(/\/$/, ''), diff --git a/src/renderer/features/servers/components/edit-server-form.tsx b/src/renderer/features/servers/components/edit-server-form.tsx index 05717c226..7f032fecf 100644 --- a/src/renderer/features/servers/components/edit-server-form.tsx +++ b/src/renderer/features/servers/components/edit-server-form.tsx @@ -50,6 +50,7 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer const form = useForm({ initialValues: { + isAdmin: server?.isAdmin, legacyAuth: false, name: server?.name, password: password || '', @@ -94,6 +95,7 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer const serverItem: ServerListItemWithCredential = { credential: data.credential, id: server.id, + isAdmin: data.isAdmin, name: values.name, type: values.type, url: values.url, diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index aabbdad8e..5aaf3d010 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -120,6 +120,7 @@ export const useCurrentServer = () => return { features: state.currentServer?.features, id: state.currentServer?.id, + isAdmin: state.currentServer?.isAdmin, musicFolderId: state.currentServer?.musicFolderId, name: state.currentServer?.name, preferInstantMix: state.currentServer?.preferInstantMix, @@ -132,6 +133,14 @@ export const useCurrentServer = () => }; }, shallow) as ServerListItem; +export const useIsAdmin = () => + useAuthStore((state) => { + return { + isAdmin: state.currentServer?.isAdmin ?? false, + userId: state.currentServer?.userId, + }; + }, shallow); + export const useCurrentServerWithCredential = () => useAuthStore((state) => state.currentServer) as ServerListItemWithCredential; @@ -146,3 +155,14 @@ export const getServerById = (id?: string) => { return useAuthStore.getState().actions.getServer(id); }; + +export const usePermissions = () => { + const { isAdmin, userId } = useIsAdmin(); + + return { + playlists: { + editPublic: isAdmin, + }, + userId: userId, + }; +}; diff --git a/src/shared/api/subsonic/subsonic-types.ts b/src/shared/api/subsonic/subsonic-types.ts index 7a8394118..b623e16b5 100644 --- a/src/shared/api/subsonic/subsonic-types.ts +++ b/src/shared/api/subsonic/subsonic-types.ts @@ -7,7 +7,24 @@ const baseResponse = z.object({ }), }); -const authenticate = z.null(); +const authenticate = z.object({ + user: z.object({ + adminRoles: z.boolean(), + commentRole: z.boolean(), + coverArtRole: z.boolean(), + downloadRole: z.boolean(), + folder: z.string().array(), + jukeboxRole: z.boolean(), + playlistRole: z.boolean(), + podcastRole: z.boolean(), + scrobblingEnabled: z.boolean(), + settingsRole: z.boolean(), + shareRole: z.boolean(), + streamRole: z.boolean(), + uploadRole: z.boolean(), + username: z.string(), + }), +}); const authenticateParameters = z.object({ c: z.string(), @@ -16,6 +33,7 @@ const authenticateParameters = z.object({ s: z.string().optional(), t: z.string().optional(), u: z.string(), + username: z.string(), v: z.string(), }); diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts index d7a695ea3..87b9882bb 100644 --- a/src/shared/types/domain-types.ts +++ b/src/shared/types/domain-types.ts @@ -86,6 +86,7 @@ export type QueueSong = Song & { export type ServerListItem = { features?: ServerFeatures; id: string; + isAdmin?: boolean; musicFolderId?: string[]; name: string; preferInstantMix?: boolean; @@ -237,6 +238,7 @@ export type Artist = { export type AuthenticationResponse = { credential: string; + isAdmin?: boolean; ndCredential?: string; userId: null | string; username: string;