# Register App Implementation Tasks

## Goal

Create a clear follow-up plan for user privileges (admin/member) and forgot-password flow hardening.

## Current Baseline

- Admin check already routes through `require_admin` -> `user_is_admin`.
- `user_is_admin` supports:
  - Group membership (`groups` + `user_group`)
  - `users.is_admin` fallback
  - `users.role` fallback
- Forgot-password UI already exists in `public/reset_password.php`.
- Admin password reset UI exists in `public/admin/password_reset.php`.

## Phase 1: Decide Privilege Model

- [ ] Choose one primary model for production:
  - RBAC tables (`groups`, `user_group`, `group_application`) as primary
  - `users.is_admin` boolean as primary
  - `users.role` enum/string as primary
- [ ] Keep fallback behavior documented so local/dev databases do not break.

## Phase 2: Schema Alignment

- [ ] Ensure RBAC tables exist (if using group-based permissions):
  - `groups`
  - `user_group`
  - `applications`
  - `group_application`
- [ ] Add missing forgot-password columns to `users` if absent:
  - `reset_token_hash` (VARCHAR(64), nullable)
  - `reset_expires_at` (DATETIME, nullable)
- [ ] Add indexes for reset flow:
  - `INDEX idx_users_email (email)`
  - `INDEX idx_users_reset_expires_at (reset_expires_at)`

## Phase 3: Seed Privileges

- [ ] Seed `admin` and `member` groups (if RBAC model).
- [ ] Assign at least one bootstrap admin account.
- [ ] Confirm first admin can access `/public/admin/users.php`.

## Phase 4: Admin Management UX

- [ ] Add admin controls on admin users page:
  - Grant admin
  - Revoke admin
  - Optional: assign/remove groups
- [ ] Add confirmation prompts for role changes.
- [ ] Log admin changes (who changed, target user, when).

## Phase 5: Forgot-Password Security Hardening

- [ ] Replace on-screen token display with delivery via email or secure channel.
- [ ] Return generic request response text for both existing and non-existing users.
- [ ] Add request throttling:
  - Per IP limit
  - Per account/email limit
- [ ] Enforce token lifetime policy (currently 1 hour).
- [ ] Ensure single-use token invalidation after password update.
- [ ] Add audit log entries for token request and token use.

## Phase 6: Operational Config and Paths

- [ ] Keep path/url generation centralized in helper functions only.
- [ ] Do not introduce direct hardcoded app paths in page files.
- [ ] Verify all redirects and links resolve under `/apps/register/public/*` and `/apps/register/php/*`.

## Phase 7: Tests and Verification

- [ ] Verify privilege scenarios:
  - Non-admin denied from admin pages
  - Admin allowed on admin pages
  - Role/group changes take effect on next request
- [ ] Verify forgot-password scenarios:
  - Request token for known account
  - Generic response for unknown account
  - Expired token rejected
  - Invalid token rejected
  - Valid token updates password and clears token fields
- [ ] Smoke test routes after migration:
  - `/public/login.php`
  - `/public/register.php`
  - `/public/reset_password.php`
  - `/public/admin/users.php`
  - `/public/admin/password_reset.php`

## Phase 8: Login Toggle and Multi-App Security Rollout

- [ ] Add and maintain sample protected pages for auth behavior checks:
  - `/public/test_app_one.php`
  - `/public/test_app_two.php`
- [ ] Validate `AUTH_REQUIRE_LOGIN=1` behavior:
  - Logged-out user is redirected to `/public/login.php`
  - Logged-in user can open both test app pages
- [ ] Validate `AUTH_REQUIRE_LOGIN=0` behavior:
  - Logged-out user can open both test app pages as `guest`
  - Page explicitly shows login mode ON/OFF for operator clarity
- [ ] Keep auth toggle centralized in shared auth helper (`auth_login_required`) rather than per-page conditionals.
- [ ] Verify cross-app session continuity assumptions:
  - Same session name
  - Same cookie domain/policy
  - Shared session save path
  - Shared auth/bootstrap conventions
- [ ] Use standard app security template when adding new app routes:
  - `app/secure_page_template.php`
  - `docs/APP_SECURITY_TEMPLATE.md`

## Why This Is Needed

- Avoid regressions when switching between secure mode and no-login mode during development and support.
- Give operators a repeatable, visible test path to prove auth behavior without touching production pages.
- Prevent app-by-app auth drift by centralizing login enforcement in shared auth logic.
- Reduce repeated setup work for each new app by reusing one security/bootstrap template.
- Clarify when cross-app re-login should or should not happen, reducing support confusion.

## SQL Snippets (Reference)

```sql
-- Add forgot-password columns if missing
ALTER TABLE users
  ADD COLUMN reset_token_hash VARCHAR(64) NULL,
  ADD COLUMN reset_expires_at DATETIME NULL;

-- Optional admin fallback model
ALTER TABLE users
  ADD COLUMN is_admin TINYINT(1) NOT NULL DEFAULT 0;

-- Grant one admin user by email
UPDATE users SET is_admin = 1 WHERE email = 'you@example.com';
```
```sql
<?php
echo password_hash("admin1234", PASSWORD_BCRYPT);

-- add user with password
INSERT INTO users (
    username,
    email,
    password_hash,
    is_active,
    role,
    comment,
    is_admin
) VALUES (
    'rick',
    'rick@admin.com',
    '$2y$10$wH8Q9uQeX9Zf7eTq8Yp8UuJmH8qQeX9Zf7eTq8Yp8UuJmH8qQeX9G',
    1,
    'user',
    'admin1234',
    1
);
```


## Definition of Done

- [ ] No fatal errors when RBAC tables are missing.
- [ ] At least one intentional and tested admin assignment path.
- [ ] Forgot-password flow is secure enough for production (no token leakage in UI).
- [ ] Route/path behavior remains centrally controlled and upgrade-safe.
- [ ] `AUTH_REQUIRE_LOGIN` mode is test-verified in both ON and OFF states.
- [ ] New app routes can be scaffolded from a documented security template.

## FAQ

### How do I change an existing user to admin?

Use one of the supported admin models in `app/auth.php` (`user_is_admin`).

#### Option A: Group-based admin (recommended)

```sql
-- Ensure admin group exists
INSERT INTO groups (group_name, group_slug)
VALUES ('Administrators', 'admin')
ON DUPLICATE KEY UPDATE group_name = VALUES(group_name);

-- Add user to admin group
INSERT IGNORE INTO user_group (user_id, group_id)
SELECT u.id, g.id
FROM users u
JOIN groups g ON g.group_slug = 'admin'
WHERE u.email = 'you@example.com';
```

#### Option B: is_admin fallback

```sql
ALTER TABLE users ADD COLUMN is_admin TINYINT(1) NOT NULL DEFAULT 0;
UPDATE users SET is_admin = 1 WHERE email = 'you@example.com';
```

#### Option C: role fallback

```sql
ALTER TABLE users ADD COLUMN role VARCHAR(30) NULL;
UPDATE users SET role = 'admin' WHERE email = 'you@example.com';
```

### How do I remove admin from a user?

```sql
-- Group-based
DELETE ug
FROM user_group ug
JOIN groups g ON g.id = ug.group_id
JOIN users u ON u.id = ug.user_id
WHERE g.group_slug = 'admin'
  AND u.email = 'you@example.com';

-- is_admin fallback
UPDATE users SET is_admin = 0 WHERE email = 'you@example.com';

-- role fallback
UPDATE users SET role = 'member' WHERE email = 'you@example.com';
```

### How can I verify admin is working?

1. Log in as the updated user.
2. Open `/apps/register/public/admin/users.php`.
3. Expected result: admin user can access page; non-admin receives HTTP 403.
