# Users & groups

Source: https://docs.externa.qiick.io/docs/users-and-groups

Admin **Users** and **Groups** manage who can sign in and how they inherit roles. Both support soft-delete, restore, force-delete, and bulk actions. Groups attach members and Spatie roles so permissions (and role-tied collection/file ACL) flow through `EffectivePermissionResolver`.

<!-- screenshot omitted -->

<!-- screenshot omitted -->

> **Related**
> How group roles become effective permissions: [Effective permissions](/docs/permissions). Role assignment UI: [Roles & permissions](/docs/roles-and-permissions). Drawer URLs: [Routing — deep-links](/docs/routing#admin-ui-deep-links-drawers-panels) (`?edit=` / `?new=1`).

## Permissions

Exact `PermissionEnum` strings (do not invent others):

| Area   | Permissions                                                                                                                     |
| ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Users  | `can-show-users`, `can-create-users`, `can-edit-users`, `can-delete-users`, `can-restore-users`, `can-force-delete-users`       |
| Groups | `can-show-groups`, `can-create-groups`, `can-edit-groups`, `can-delete-groups`, `can-restore-groups`, `can-force-delete-groups` |

Route middleware uses `permission:…` for most endpoints. Bulk actions authorize inside `BulkUserActionRequest` / `BulkUserGroupActionRequest` (maps `delete` / `restore` / `force_delete` to the matching permission).

### Super-admin principals (core 1.3.0+)

Only a signed-in **super-admin** may grant the super-admin role (or groups that carry it) or create / update / delete / restore users and groups that are already super-admin. The same rules apply to bulk actions and AI `ManageUsers` / `ManageGroups` tools ([core PR #307](https://github.com/qiick-io/externa-core/pull/307)). Non–super-admin managers with `can-*-users` / `can-*-groups` still manage ordinary principals.

## Admin lists (1.4.0)

The users, groups, roles, API keys, permissions, activity log, jobs and pending tool calls lists are **server-driven data tables** ([core #523](https://github.com/qiick-io/externa-core/pull/523), [#526–#528](https://github.com/qiick-io/externa-core/pull/526), [#530](https://github.com/qiick-io/externa-core/pull/530), [#533](https://github.com/qiick-io/externa-core/pull/533), [#535](https://github.com/qiick-io/externa-core/pull/535), [#537](https://github.com/qiick-io/externa-core/pull/537), [#561](https://github.com/qiick-io/externa-core/pull/561)):

- **Sorting**: sortable column headers open an ascending / descending menu. Sorting, filtering and paging run on the server and stay in the URL, so a list can be shared or reloaded as it is.
- **Faceted filters**: filter buttons (for example role or status) next to the search.
- **Row actions**: icon actions at the end of each row; clicking the row opens the record. Row checkboxes and select-all drive the bulk actions.
- **Paging**: a footer with the range, the pages and rows per page (10 / 20 / 50 / 100, kept in `per_page`).
- **Delete confirmations**: deleting from a list, a bulk action or a row menu asks for confirmation first.

All admin tables share one 41px row height with small avatars ([core #585](https://github.com/qiick-io/externa-core/pull/585), [#586](https://github.com/qiick-io/externa-core/pull/586)).

## Users

### Controller & page

| Item       | Detail                                                                                                              |
| ---------- | ------------------------------------------------------------------------------------------------------------------- |
| Controller | `App\Http\Controllers\Admin\UserController`                                                                         |
| Model      | `App\Models\User` (`SoftDeletes`, Spatie `HasRoles`, Fortify `TwoFactorAuthenticatable` + `PasskeyAuthenticatable`) |
| Frontend   | `resources/js/pages/admin/users/index.tsx`                                                                          |
| Routes     | `routes/admin.php` under `auth` + `verified`                                                                        |

### Lifecycle

| Action                       | Route                        | Permission                |
| ---------------------------- | ---------------------------- | ------------------------- |
| List / search / trash filter | `GET /users`                 | `can-show-users`          |
| Create                       | `POST /users`                | `can-create-users`        |
| Update                       | `PUT /users/{user}`          | `can-edit-users`          |
| Soft-delete                  | `DELETE /users/{user}`       | `can-delete-users`        |
| Restore                      | `POST /users/{user}/restore` | `can-restore-users`       |
| Force-delete                 | `DELETE /users/{user}/force` | `can-force-delete-users`  |
| Bulk                         | `POST /users/bulk-actions`   | Per action in FormRequest |

Route binding uses `withTrashed()` so restore/force-delete can target soft-deleted users.

**Bulk actions** (`BulkUserActionRequest`): `action` ∈ `delete` | `restore` | `force_delete`, plus `ids[]`.

### Create / update fields

Validated by `StoreUserRequest` / `UpdateUserRequest`:

| Field        | Notes                                       |
| ------------ | ------------------------------------------- |
| `first_name` | Required                                    |
| `last_name`  | Optional                                    |
| `email`      | Required, unique                            |
| `username`   | Optional                                    |
| `password`   | Required on create; optional on update      |
| `is_active`  | Optional boolean                            |
| `role_ids`   | Optional array — synced via Spatie          |
| `group_ids`  | Optional array — synced on `groups()` pivot |

### last_login

On `Illuminate\Auth\Events\Login`, `AppServiceProvider::registerAuthActivityListeners` updates:

- `last_login_at`
- `last_login_ip`

via `forceFill(...)->saveQuietly()` on the user.

> Those columns exist and are maintained on login. They are **not** currently exposed in `UserResource` or the admin users table (not listed/sortable in the UI).

## Groups

### Why groups

Groups are a thin inheritance layer for large user bases: attach Spatie roles to a group, add members, and every member inherits:

1. **Admin Spatie permissions** from those roles (via `EffectivePermissionResolver`)
2. **Collection / file ACL** tied to the same roles (`collection_permissions` / `file_permissions` matrices on the role)

There is **no** separate group → collection_permissions matrix. Configure access on the role; scale membership via the group.

### Controller & page

| Item       | Detail                                                                       |
| ---------- | ---------------------------------------------------------------------------- |
| Controller | `App\Http\Controllers\Admin\UserGroupController`                             |
| Model      | `App\Models\UserGroup` (`SoftDeletes`, `resolveRouteBinding` includes trash) |
| Pivots     | `user_group_user` (members), `user_group_role` (roles)                       |
| Frontend   | `resources/js/pages/admin/groups/index.tsx`                                  |

### Routes

| Action                       | Route                          | Permission                |
| ---------------------------- | ------------------------------ | ------------------------- |
| List / search / trash filter | `GET /groups`                  | `can-show-groups`         |
| Create                       | `POST /groups`                 | `can-create-groups`       |
| Update                       | `PUT /groups/{group}`          | `can-edit-groups`         |
| Soft-delete                  | `DELETE /groups/{group}`       | `can-delete-groups`       |
| Restore                      | `POST /groups/{group}/restore` | `can-restore-groups`      |
| Force-delete                 | `DELETE /groups/{group}/force` | `can-force-delete-groups` |
| Bulk                         | `POST /groups/bulk-actions`    | Per action in FormRequest |

**Bulk actions** (`BulkUserGroupActionRequest`): `action` ∈ `delete` | `restore` | `force_delete`, plus `ids[]`.

### Members + role attachment

Store/update accept:

| Field         | Role                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------ |
| `name`        | Required                                                                                         |
| `description` | Optional                                                                                         |
| `user_ids`    | Optional — sync group members when present (omit to leave members unchanged; empty array clears) |
| `role_ids`    | Optional — sync Spatie roles onto the group when present                                         |

Index payloads include `user_ids` and `users` (id / name / email) so the edit drawer can hydrate the member multi-select.

**Permission inheritance:** `EffectivePermissionResolver` unions:

1. Roles assigned directly on the user
2. Roles attached to each of the user’s groups

`effectiveRoleIds()` feeds the same set into admin collection field ACL (`CollectionPermissionGuard::rulesForUser`). See [Effective permissions](/docs/permissions).

## Source map

| Concern              | Location                                                         |
| -------------------- | ---------------------------------------------------------------- |
| Users controller     | `app/Http/Controllers/Admin/UserController.php`                  |
| Groups controller    | `app/Http/Controllers/Admin/UserGroupController.php`             |
| Admin routes         | `routes/admin.php`                                               |
| Client route helpers | `resources/js/lib/admin-routes.ts`                               |
| Auth login timestamp | `AppServiceProvider::registerAuthActivityListeners`              |
| Post-login home      | `App\Support\Auth\HomePath` (uses `EffectivePermissionResolver`) |
