Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/concierge/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { routes } from './app.routes';

import * as Sentry from '@sentry/angular';

import { PLACEOS_APP_ACCESS } from '@placeos/components';

import {
LocaleService,
registerActiveLocale,
Expand Down Expand Up @@ -74,6 +76,15 @@ export const appConfig: ApplicationConfig = {
useFactory: (localeService: LocaleService) => localeService.locale,
},
{ provide: TitleStrategy, useClass: SettingsTitleStrategy },
{
provide: PLACEOS_APP_ACCESS,
useValue: {
// Concierge shows everyone's bookings, visitors and the staff
// directory, so without `app.allow_access_groups` it is for
// admin and support users only.
default_groups: ['placeos_admin', 'placeos_support'],
},
},
provideHttpClient(withXhr(), withInterceptorsFromDi()),
],
};
1 change: 1 addition & 0 deletions docs/settings/concierge.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Each locale entry requires an `id` and `name`:
| `features` | string[] | All features enabled (see below) | List of features enabled in the app. Available values: `spaces`, `zones`, `desks`, `parking`, `visitors`, `assets`, `facilities`, `internal-users`, `points`, `surveys`, `catering`, `events`, `booking-report`, `attendance-report`, `contact-tracing-report`, `signage`, `email-templates`. |
| `feature_groups` | object | – | Maps feature names to the user groups allowed to access them, e.g. `parking-requests`. Features without an entry are available to all users. |
| `admin_group` | string | `"concierge_admins"` | Name of the user group that grants access to admin sections of the app. Members of `placeos_admin` and `placeos_support` are always allowed. |
| `allow_access_groups` | string[] | – | User groups allowed to open the app at all. When unset, only members of `placeos_admin` and `placeos_support` (admin and support users) are admitted; set it to open the app to a front-desk group. |
| `delegated` | boolean | `false` | Set when your PlaceOS instance uses delegated calendar access. Hides event actions from the event details view. |
| `has_uploads` | boolean | `true` | Whether file upload support (S3/cloud storage) is available in the app. |
| `bulk_actions` | boolean | `false` | Let staff select several rows and apply one action to all of them. Desk, parking and locker bookings, parking requests and asset requests get approve and reject actions. Catering orders get a status action. |
Expand Down
14 changes: 12 additions & 2 deletions libs/components/src/lib/authorised-user.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ function resolvedWithin(promise: Promise<unknown>, delay: number) {
}

export abstract class PLACEOS_APP_ACCESS {
public readonly group: string;
/** A single group that gates the app, regardless of settings. */
public readonly group?: string;
/**
* Groups that gate the app while `app.allow_access_groups` is not set.
* Without them an unconfigured app admits every signed-in user.
*/
public readonly default_groups?: string[];
}

@Injectable({
Expand Down Expand Up @@ -98,9 +104,13 @@ export class AuthorisedUserGuard {
]),
);
if (!state_ready) return this.offlineAccess();
const configured: string[] =
this._settings.get('app.allow_access_groups') || [];
const groups = this._access?.group
? [this._access.group]
: this._settings.get('app.allow_access_groups') || [];
: configured.length
? configured
: this._access?.default_groups || [];
const use_group_subsystem_access = await this.useGroupSubsystemAccess();
let can_activate = false;
if (use_group_subsystem_access) {
Expand Down
30 changes: 29 additions & 1 deletion libs/components/src/tests/authorised-user.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {

describe('AuthorisedUserGuard', () => {
let spectator: SpectatorService<AuthorisedUserGuard>;
const access_mock = { group: '' };
const access_mock: { group: string; default_groups?: string[] } = {
group: '',
};
const settings_mock = {
app_name: 'workplace',
get: vi.fn((key: string): any => []),
Expand Down Expand Up @@ -54,6 +56,7 @@ describe('AuthorisedUserGuard', () => {
beforeEach(() => {
vi.clearAllMocks();
access_mock.group = '';
delete access_mock.default_groups;
settings_mock.app_name = 'workplace';
settings_mock.get.mockImplementation((key) =>
key === 'app.allow_access_groups' ? [] : undefined,
Expand Down Expand Up @@ -184,6 +187,31 @@ describe('AuthorisedUserGuard', () => {
access_mock.group = '';
});

it('should fall back to the app default groups when none are configured', async () => {
access_mock.default_groups = ['placeos_admin', 'placeos_support'];
const router = spectator.inject(Router);

setCurrentUser({ groups: [] } as any);
await expect(spectator.service.canActivate()).resolves.toBe(false);
expect(router.navigate).toHaveBeenCalledWith(['/unauthorised']);

setCurrentUser({ groups: ['placeos_support'] } as any);
await expect(spectator.service.canActivate()).resolves.toBe(true);
});

it('should prefer configured access groups over the app default groups', async () => {
access_mock.default_groups = ['placeos_admin'];
settings_mock.get.mockImplementation((key) =>
key === 'app.allow_access_groups' ? ['front_desk'] : undefined,
);

setCurrentUser({ groups: ['front_desk'] } as any);
await expect(spectator.service.canActivate()).resolves.toBe(true);

setCurrentUser({ groups: ['placeos_admin'] } as any);
await expect(spectator.service.canActivate()).resolves.toBe(false);
});

it('waits for user groups and app settings to load', async () => {
common_lib.user_groups_loaded.set(false);
let resolve_settings: () => void;
Expand Down
Loading