Skip to content

Allow anonymous access to welcome page #1129

@IanMayo

Description

@IanMayo

By default, the welcome page (and app in general) requires login/auth. But, we could allow anonymous access - this could help analysts learn about new data/records.

So, all resources would be publicly visible, but only auth users would have action buttons, or edit/save/cancel buttons.

image

We'd obvs have to enforce this in the backend. I suspect we will have to introduce a zero role_id, that is used for users that are not logged in . This role will have read access to all tables, plus write access to the requests table.

Update: it's possible to do this with minimal change:

  1. Admin creates user via VAL UI:
    • username: browse-only
    • Assign read-only role
    • Set temp password
  2. Login as browse-only (within 60 min):
    • Use temp password
    • Self-change to permanent password (e.g., browse2024!)
  3. Manually edit database:
  UPDATE _users
  SET lastUpdatedAt = '9999-12-31T00:00:00Z',
      updateBefore = ''
  WHERE username = 'browse-only';

Also, modify the _user_role for that user to be the default role (this isn't currently possible via the UI).

  1. Frontend Login button:
    <Button onClick={() => login({
    username: 'browse-only',
    password: 'browse2024!'
    })}>
    Browse as Guest

VAL Browse-Only Access - Permission Implementation Analysis

Current Permission Implementation

1. Resource Routes (App.tsx:479+)

  • protectedRoutes() function removes edit/create routes when write: false
  • Already works - browse-only users won't have edit/create routes available

2. List Actions - CreateButtons

All protected with useCanAccess:

  • BatchList.tsx:35-38
{hasAccess(R_BATCHES, {write: true}) ? <CreateButton label='ADD NEW BATCH' /> : <></>}
  • UserList.tsx:40-46
{hasWriteAccess ? <CreateButton to={`${basePath}/create`} /> : null}
// where: hasWriteAccess = hasAccess(R_USERS, {write: true})
  • ReferenceDataList.tsx:45-47
{hasAccess('reference-data', {write: true}) ? <CreateButton to={'create'} /> : null}
  • BatchShow.tsx:68-74 (nested CreateButton for items within batch)
{hasAccess(R_ITEMS, {write: true}) ? (
  <CreateButton label='ADD ITEM' to={`/${R_RICH_ITEMS}/create?batch=${batch}`} />
) : <></>}

3. Show Actions - EditButtons

All Show components protected:

  • ItemShow.tsx:376-378
{hasAccess(R_ITEMS, {write: true}) && <EditButton to={`/${R_RICH_ITEMS}/${record.id}`} />}
  • BatchShow.tsx:44
{hasAccess(R_BATCHES, {write: true}) && <EditButton />}
  • UserShow.tsx:531
{hasWriteAccess && <EditButton />}
// where: hasWriteAccess = hasAccess(R_USERS, {write: true})
  • DispatchShow.tsx:59-60
{hasAccess(R_DISPATCH, {write: true}) && !dispatched && <EditButton />}
  • DestructionShow.tsx:62-63
{hasAccess(R_DESTRUCTION, {write: true}) && !finalised && <EditButton />}
  • ProjectShow.tsx:53-55
{hasAccess(R_PROJECTS, {write: true}) ? <><EditButton /><CreateButton /></> : null}
  • ReferenceDataShow.tsx:35
{hasAccess(resource, {write: true}) ? <EditButton /> : null}

4. Bulk Actions

ItemList.tsx - Comprehensive protection:

  • Line 492 - Loan buttons:
const disableLoanItemsBulkActions = !(
  canBeLoaned &&
  hasAccess(R_ITEMS, {write: true}) &&
  isSelected
)
  • Lines 498-504 - Dispatch/Destroy/Change Location:
const normalWithWriteAccess =
  isItemNormal && hasAccess(R_ITEMS, {write: true}) && isSelected

const { disableDispatch, disableDestroy, disableChangeLocation } = {
  disableDispatch: !(normalWithWriteAccess && dispatch),
  disableDestroy: !(normalWithWriteAccess && destroy),
  disableChangeLocation: !(normalWithWriteAccess && location)
}

Lists with bulkActionButtons disabled:

  • BatchList.tsx:177 - bulkActionButtons={false}
  • Recent.tsx:169 - bulkActionButtons={false}
  • ReferenceDataList.tsx:66 - bulkActionButtons={false}

Custom bulk actions (read-only):

  • UserList.tsx:77 - bulkActionButtons={<UserActions />}
    • Shows "User Muster List" button (read-only report generation)

5. Other Protected Actions

UserShow.tsx:

  • Line 435 - "Depart Organisation" button:
disabled={cannotDepart()}
// cannotDepart checks: !hasWriteAccess || hasLoanedItems || alreadyDeparted
  • Line 533 - "Edit Password" button (role-based):
{userDetails && rolesThatCanEditPassword.includes(userDetails.userRole) ? (
  <Button onClick={handleEditPasswordOpen}>Edit Password</Button>
) : null}

DispatchShow.tsx:

  • Line 94 - Footer mutation buttons check:
const hasWritePermission = hasAccess(R_ITEMS, {write: true})
  • Disables: Dispatch, Print Receipt, Print Hastener, Record Hastener Sent, Receipt Note Received

DestructionShow.tsx:

  • Line 89 - Footer mutation buttons check:
const hasWritePermission = hasAccess(R_ITEMS, {write: true})
  • Disables: Destroy, Destruction Certificate

6. Core Permission Hook

useCanAccess.ts:36-64 - protectedRoutes() function:

export const protectedRoutes = (
  permissions: ResourcePermissions,
  resource: string,
  routes: ResourceRoutes
): ResourceRoutes => {
  const permission = permissions[resource] ?? { read: false, write: false }
  const { write, read } = permission

  const hasReadPermission = !!(read)
  const hasWritePermission = !!(hasReadPermission && write)

  return {
    create: hasWritePermission ? routes.create : undefined,
    edit: hasWritePermission ? routes.edit : undefined,
    list: hasReadPermission ? routes.list : undefined,
    show: hasReadPermission ? routes.show : undefined
  }
}

Used throughout App.tsx to control route availability based on permissions.


Summary

✅ Complete Protection Coverage

The codebase has comprehensive permission checking already in place:

  1. Route-level protection - protectedRoutes() removes edit/create routes for read-only users
  2. Component-level protection - All CreateButtons and EditButtons check hasAccess()
  3. Action-level protection - All bulk actions and mutation buttons check permissions
  4. Consistent pattern - Uses useCanAccess() hook uniformly across all components

🎯 Implications for Browse-Only Feature

No frontend code changes required for permission enforcement!

The browse-only feature will work automatically once:

  1. Browse-only user exists in database
  2. User has read-only role assigned (all resources: read: true, write: false, delete: false)
  3. Login page has "Browse as Guest" button

All UI elements (EditButtons, CreateButtons, BulkActions, mutation operations) will automatically hide/disable based on the user's permissions returned by the auth provider.


Resources Covered

  • ✅ Items (R_ITEMS, R_RICH_ITEMS, R_ALL_ITEMS)
  • ✅ Batches (R_BATCHES)
  • ✅ Projects (R_PROJECTS)
  • ✅ Dispatch (R_DISPATCH)
  • ✅ Destruction (R_DESTRUCTION)
  • ✅ Users (R_USERS)
  • ✅ Vault Locations (R_VAULT_LOCATION)
  • ✅ Addresses (R_ADDRESSES)
  • ✅ Audit (R_AUDIT)
  • ✅ All Reference Data (Protective Marking, Cat Codes, etc.)

Implementation Quality Notes

Strengths:

  • Consistent use of useCanAccess hook across all components
  • Multi-layer protection (routes, components, actions)
  • Write permission implies read permission (proper hierarchy)
  • Disabled states for buttons (better UX than hiding)

Architecture:

  • Central permission system in authProvider/permissions.ts
  • Reusable useCanAccess() hook
  • protectedRoutes() helper for route-level control
  • Permission data flows from backend through auth provider

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Status

    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions