feat: enforce per-user module access in Makelore
This commit is contained in:
@@ -9,6 +9,11 @@ import {
|
||||
WORKS_SQUARE_ACTIVITY_SYNC_INTERVAL_MS,
|
||||
WORKS_SQUARE_SESSION_IDLE_TIMEOUT_MS,
|
||||
} from '../../shared/auth-session';
|
||||
import {
|
||||
DEFAULT_MODULE_ACCESS,
|
||||
normalizeModuleAccess,
|
||||
type ModuleAccess,
|
||||
} from '../../shared/module-access';
|
||||
|
||||
export type AuthUser = {
|
||||
username: string;
|
||||
@@ -51,6 +56,10 @@ type MainSessionResponse = AuthActionResponse & {
|
||||
session?: MainSession | null;
|
||||
};
|
||||
|
||||
type ModuleAccessResponse = AuthActionResponse & {
|
||||
moduleAccess?: unknown;
|
||||
};
|
||||
|
||||
type RefreshSessionOptions = {
|
||||
forceRefresh?: boolean;
|
||||
};
|
||||
@@ -69,6 +78,7 @@ type AuthState = {
|
||||
/** One-release bridge for moving old Renderer-persisted refresh tokens into Main. */
|
||||
legacyRefreshToken: string | null;
|
||||
user: AuthUser | null;
|
||||
moduleAccess: ModuleAccess;
|
||||
init: () => Promise<void>;
|
||||
loginWithBrowser: () => Promise<void>;
|
||||
refreshSession: (options?: RefreshSessionOptions) => Promise<string | null>;
|
||||
@@ -155,6 +165,7 @@ function getClearedSession() {
|
||||
canRefresh: false,
|
||||
legacyRefreshToken: null,
|
||||
user: null,
|
||||
moduleAccess: { ...DEFAULT_MODULE_ACCESS },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -220,6 +231,16 @@ async function syncMainSession(session: {
|
||||
}
|
||||
}
|
||||
|
||||
async function readCurrentModuleAccess(fallback: ModuleAccess): Promise<ModuleAccess> {
|
||||
try {
|
||||
const response = await hostApiFetch<ModuleAccessResponse>('/api/auth/me');
|
||||
if (!response.success) return fallback;
|
||||
return normalizeModuleAccess(response.moduleAccess);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
@@ -326,7 +347,11 @@ export const useAuthStore = create<AuthState>()(
|
||||
return;
|
||||
}
|
||||
|
||||
set({ initialized: true, loading: false, error: null });
|
||||
const moduleAccess = await readCurrentModuleAccess(
|
||||
normalizeModuleAccess(state.moduleAccess),
|
||||
);
|
||||
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
|
||||
set({ initialized: true, loading: false, error: null, moduleAccess });
|
||||
},
|
||||
|
||||
loginWithBrowser: async () => {
|
||||
@@ -343,6 +368,9 @@ export const useAuthStore = create<AuthState>()(
|
||||
}
|
||||
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
|
||||
|
||||
const moduleAccess = await readCurrentModuleAccess({ ...DEFAULT_MODULE_ACCESS });
|
||||
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
|
||||
|
||||
set({
|
||||
initialized: true,
|
||||
loading: false,
|
||||
@@ -351,6 +379,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
clientId: DEFAULT_CLIENT_ID,
|
||||
...sessionFieldsFromMain(session),
|
||||
user: createUserFromToken(response.token),
|
||||
moduleAccess,
|
||||
});
|
||||
} catch (error) {
|
||||
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
|
||||
@@ -389,11 +418,15 @@ export const useAuthStore = create<AuthState>()(
|
||||
throw new Error(response.error || 'Refresh failed');
|
||||
}
|
||||
|
||||
const moduleAccess = await readCurrentModuleAccess(state.moduleAccess);
|
||||
if (!isCurrentAuthSessionEpoch(operationEpoch)) return null;
|
||||
|
||||
set({
|
||||
initialized: true,
|
||||
loading: false,
|
||||
error: null,
|
||||
...sessionFieldsFromMain(session),
|
||||
moduleAccess,
|
||||
});
|
||||
return session.accessToken;
|
||||
} catch (error) {
|
||||
@@ -589,7 +622,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
}),
|
||||
{
|
||||
name: 'niancode-auth',
|
||||
version: 1,
|
||||
version: 2,
|
||||
migrate: (persistedState: unknown) => {
|
||||
const state = persistedState && typeof persistedState === 'object'
|
||||
? persistedState as Record<string, unknown>
|
||||
@@ -601,6 +634,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
...rest,
|
||||
canRefresh: state.canRefresh === true || Boolean(legacyRefreshToken),
|
||||
legacyRefreshToken,
|
||||
moduleAccess: normalizeModuleAccess(state.moduleAccess),
|
||||
};
|
||||
},
|
||||
partialize: (state) => ({
|
||||
@@ -613,6 +647,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
canRefresh: state.canRefresh,
|
||||
legacyRefreshToken: state.legacyRefreshToken,
|
||||
user: state.user,
|
||||
moduleAccess: state.moduleAccess,
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user