> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobile-starter.amisi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> Store per-user PIN state, use biometrics, and enforce inactivity locks.

# @amisi-mobile-starter/security

`@amisi-mobile-starter/security` exports per-user security storage, PIN verifier helpers, biometric helpers, an external lock store, and two hooks.

## PIN helpers

```ts theme={null}
import {
  PIN_LENGTH,
  clearPinForUser,
  getSecuritySetupStateForUser,
  savePinForUser,
  verifyPinForUser,
} from '@amisi-mobile-starter/security';

await savePinForUser('user-123', '123456');
const isValid = await verifyPinForUser('user-123', '123456');
const setup = getSecuritySetupStateForUser('user-123');
await clearPinForUser('user-123');
```

Every storage helper requires a `userId`. There are no exported `setupPin`, `verifyPin`, or `getUserSecuritySetupState` functions.

`PIN_LENGTH` is `6`. `PIN_KEYPAD_ROWS` and `PinKeypadValue` are exported for keypad UI. `savePinForUser` stores a salted `PinVerifierPayload` in secure storage and stores setup flags in key-value storage. It does not store the PIN string.

Lower-level exports are `createPinVerifierPayload(pin)` and `verifyPinAgainstPayload(pin, payload)`.

## Biometrics

```ts theme={null}
import {
  authenticateWithBiometrics,
  getBiometricAvailability,
} from '@amisi-mobile-starter/security';

const availability = await getBiometricAvailability();
if (availability.isAvailable) {
  const result = await authenticateWithBiometrics('Unlock AMiSi');
}
```

`authenticateWithBiometrics(promptMessage: string)` requires the prompt. Its result is `{ success: boolean; errorMessage: string | null }`. Availability is `{ isAvailable, hasHardware, isEnrolled }`. The web implementation reports availability and authentication success without a native prompt.

Persist setup state with `setHasBiometricsSetupForUser(userId, value)` and `setBiometricsEnabledForUser(userId, value)`.

## Lock state

```tsx theme={null}
import { useSecurityLockState } from '@amisi-mobile-starter/security';

export const LockStatus = () => {
  const {
    isLocked,
    lockedUserId,
    pendingRoute,
    lastInteractionAt,
    backgroundAt,
  } = useSecurityLockState();

  return null;
};
```

The lock store is a set of functions, not a `lockStore` object. Key exports include:

* `initializeLockStateForUser(userId)`
* `setCurrentUserForLockStore(userId | null)`
* `lockUserSession(userId, route | null)`
* `unlockUserSession(userId)`
* `consumePendingUnlockRoute(userId)`
* `getLockState()` and `subscribeLockState(listener)`
* `markLockStoreInteraction()`, `markLockStoreBackground()`, and `consumeBackgroundDuration()`

Lock and pending-route values persist in key-value storage. The in-memory `SecurityLockState` mirrors `isLocked`, `lockedUserId`, `pendingRoute`, `lastInteractionAt`, and `backgroundAt`.

## Inactivity manager

```tsx theme={null}
import { useAppLockManager } from '@amisi-mobile-starter/security';

const { recordActivity } = useAppLockManager({
  userId: 'user-123',
  canLock: true,
  isLocked: false,
  pathname: '/profile',
  isInUnlockRoute: false,
  foregroundInactivityLockSeconds: 60,
  backgroundLockSeconds: 60,
});
```

`useAppLockManager` accepts all seven fields above and returns only `recordActivity`. It records the current route when it locks, enforces a minimum one-second timeout, watches React Native `AppState`, and exports `notifyAppInteraction()` so the root touch handler can reset foreground inactivity.
