mirror of https://github.com/buster-so/buster.git
Merge pull request #1185 from buster-so/wells-bus-1965-update-frontend-to-use-password-for-public-access-when-it-is
Wells bus 1965 update frontend to use password for public access when it is
This commit is contained in:
commit
c105a94738
|
@ -19,6 +19,7 @@ import { HTTPException } from 'hono/http-exception';
|
|||
import yaml from 'js-yaml';
|
||||
import { getPubliclyEnabledByUser } from '../../../../shared-helpers/get-publicly-enabled-by-user';
|
||||
import { getMetricsInAncestorAssetFromMetricIds } from '../../../../shared-helpers/metric-helpers';
|
||||
import { throwUnauthorizedError } from '../../../../shared-helpers/asset-public-access';
|
||||
|
||||
interface GetDashboardHandlerParams {
|
||||
dashboardId: string;
|
||||
|
@ -130,8 +131,11 @@ export async function getDashboardHandler(
|
|||
if (!hasAccess || !effectiveRole) {
|
||||
// This should never be hit because we have already thrown errors for no public access
|
||||
console.warn(`Permission denied for user ${user.id} to dashboard ${dashboardId}`);
|
||||
throw new HTTPException(403, {
|
||||
message: "You don't have permission to view this dashboard",
|
||||
throwUnauthorizedError({
|
||||
publiclyAccessible: dashboardFile.publiclyAccessible ?? false,
|
||||
publicExpiryDate: dashboardFile.publicExpiryDate ?? undefined,
|
||||
publicPassword: dashboardFile.publicPassword ?? undefined,
|
||||
userSuppliedPassword: password,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Hono } from 'hono';
|
|||
import { HTTPException } from 'hono/http-exception';
|
||||
import { getMetricsInAncestorAssetFromMetricIds } from '../../../../shared-helpers/metric-helpers';
|
||||
import { standardErrorHandler } from '../../../../utils/response';
|
||||
import { throwUnauthorizedError } from '../../../../shared-helpers/asset-public-access';
|
||||
|
||||
export async function getReportHandler(
|
||||
reportId: string,
|
||||
|
@ -40,7 +41,12 @@ export async function getReportHandler(
|
|||
});
|
||||
|
||||
if (!permission.hasAccess || !permission.effectiveRole) {
|
||||
throw new HTTPException(403, { message: 'You do not have permission to view this report' });
|
||||
throwUnauthorizedError({
|
||||
publiclyAccessible: report.publicly_accessible ?? false,
|
||||
publicExpiryDate: report.public_expiry_date ?? undefined,
|
||||
publicPassword: report.public_password ?? undefined,
|
||||
userSuppliedPassword: password,
|
||||
});
|
||||
}
|
||||
|
||||
const metrics = await getMetricsInAncestorAssetFromMetricIds(metricIds, user);
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
import { getUserOrganizationId } from '@buster/database/queries';
|
||||
import type { AssetType } from '@buster/server-shared/assets';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import z from 'zod';
|
||||
|
||||
export const checkIfAssetIsEditable = async ({
|
||||
user,
|
||||
|
@ -48,3 +49,48 @@ export const checkIfAssetIsEditable = async ({
|
|||
throw new HTTPException(403, { message: 'You do not have permission to edit this asset' });
|
||||
}
|
||||
};
|
||||
|
||||
export const ThrowUnauthorizedErrorSchema = z.object({
|
||||
publiclyAccessible: z.boolean(),
|
||||
publicExpiryDate: z.string().optional(),
|
||||
publicPassword: z.string().optional(),
|
||||
userSuppliedPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ThrowUnauthorizedErrorParams = z.infer<typeof ThrowUnauthorizedErrorSchema>;
|
||||
|
||||
|
||||
// Decides the appropriate error to throw based on the public access settings
|
||||
export function throwUnauthorizedError(params: ThrowUnauthorizedErrorParams): never {
|
||||
const { publiclyAccessible, publicExpiryDate, publicPassword, userSuppliedPassword } = params;
|
||||
if (publiclyAccessible) {
|
||||
if (publicExpiryDate) {
|
||||
try {
|
||||
if (new Date(publicExpiryDate) < new Date()) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'Public access has expired',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
throw new HTTPException(403, {
|
||||
message: 'Public access expired',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (publicPassword) {
|
||||
if (!userSuppliedPassword) {
|
||||
throw new HTTPException(418, {
|
||||
message: 'Password required for public access',
|
||||
});
|
||||
}
|
||||
if (userSuppliedPassword !== publicPassword) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'Incorrect password for public access',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to access this asset',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -308,7 +308,7 @@ describe('metric-helpers', () => {
|
|||
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
|
||||
|
||||
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
|
||||
new HTTPException(403, { message: "You don't have permission to view this metric" })
|
||||
new HTTPException(403, { message: 'You do not have permission to access this asset' })
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -345,7 +345,7 @@ describe('metric-helpers', () => {
|
|||
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
|
||||
|
||||
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
|
||||
new HTTPException(403, { message: "You don't have permission to view this metric" })
|
||||
new HTTPException(403, { message: 'Public access expired' })
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -363,7 +363,7 @@ describe('metric-helpers', () => {
|
|||
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
|
||||
|
||||
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
|
||||
new HTTPException(403, { message: "You don't have permission to view this metric" })
|
||||
new HTTPException(418, { message: 'Password required for public access' })
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -384,7 +384,7 @@ describe('metric-helpers', () => {
|
|||
};
|
||||
|
||||
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
|
||||
new HTTPException(403, { message: "You don't have permission to view this metric" })
|
||||
new HTTPException(403, { message: 'Incorrect password for public access' })
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import { HTTPException } from 'hono/http-exception';
|
|||
import yaml from 'js-yaml';
|
||||
import { z } from 'zod';
|
||||
import { getPubliclyEnabledByUser } from './get-publicly-enabled-by-user';
|
||||
import { throwUnauthorizedError } from './asset-public-access';
|
||||
|
||||
export const MetricAccessOptionsSchema = z.object({
|
||||
/** If public access has been verified by a parent resource set to true */
|
||||
|
@ -86,8 +87,11 @@ export async function fetchAndProcessMetricData(
|
|||
effectiveRole = permissionResult.effectiveRole ? permissionResult.effectiveRole : effectiveRole;
|
||||
|
||||
if (!permissionResult.hasAccess || !effectiveRole) {
|
||||
throw new HTTPException(403, {
|
||||
message: "You don't have permission to view this metric",
|
||||
throwUnauthorizedError({
|
||||
publiclyAccessible: metricFile.publiclyAccessible ?? false,
|
||||
publicExpiryDate: metricFile.publicExpiryDate ?? undefined,
|
||||
publicPassword: metricFile.publicPassword ?? undefined,
|
||||
userSuppliedPassword: password,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -94,4 +94,4 @@ export const InputTextArea = React.forwardRef<InputTextAreaRef, InputTextAreaPro
|
|||
}
|
||||
);
|
||||
|
||||
InputTextArea.displayName = 'InputTextArea';
|
||||
InputTextArea.displayName = 'InputTextArea';
|
|
@ -33,8 +33,12 @@ export function getPermissionCacheKey(
|
|||
userId: string,
|
||||
assetId: string,
|
||||
assetType: AssetType,
|
||||
requiredRole: AssetPermissionRole
|
||||
requiredRole: AssetPermissionRole,
|
||||
password?: string
|
||||
): CacheKey {
|
||||
if (password) {
|
||||
return `${userId}:${assetId}:${assetType}:${requiredRole}:${password}`;
|
||||
}
|
||||
return `${userId}:${assetId}:${assetType}:${requiredRole}`;
|
||||
}
|
||||
|
||||
|
@ -45,9 +49,10 @@ export function getCachedPermission(
|
|||
userId: string,
|
||||
assetId: string,
|
||||
assetType: AssetType,
|
||||
requiredRole: AssetPermissionRole
|
||||
requiredRole: AssetPermissionRole,
|
||||
password?: string
|
||||
): AssetPermissionResult | undefined {
|
||||
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole);
|
||||
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole, password);
|
||||
const cached = permissionCache.get(key);
|
||||
|
||||
if (cached !== undefined) {
|
||||
|
@ -67,9 +72,10 @@ export function setCachedPermission(
|
|||
assetId: string,
|
||||
assetType: AssetType,
|
||||
requiredRole: AssetPermissionRole,
|
||||
result: AssetPermissionResult
|
||||
result: AssetPermissionResult,
|
||||
password?: string
|
||||
): void {
|
||||
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole);
|
||||
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole, password);
|
||||
permissionCache.set(key, result);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
|
|||
} = check;
|
||||
|
||||
// Check cache first (only for single role checks)
|
||||
const cached = getCachedPermission(userId, assetId, assetType, requiredRole);
|
||||
const cached = getCachedPermission(userId, assetId, assetType, requiredRole, userSuppliedPassword);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
@ -81,10 +81,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
|
|||
if (dbResult.accessPath !== undefined) {
|
||||
result.accessPath = dbResult.accessPath;
|
||||
}
|
||||
// Only cache single role checks
|
||||
if (!Array.isArray(requiredRole)) {
|
||||
setCachedPermission(userId, assetId, assetType, requiredRole, result);
|
||||
}
|
||||
setCachedPermission(userId, assetId, assetType, requiredRole, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +124,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
|
|||
effectiveRole: accessRole,
|
||||
accessPath: 'public' as const,
|
||||
};
|
||||
setCachedPermission(userId, assetId, assetType, requiredRole, result);
|
||||
setCachedPermission(userId, assetId, assetType, requiredRole, result, userSuppliedPassword);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue