-
Notifications
You must be signed in to change notification settings - Fork 9
Develop #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Develop #139
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { api } from '../common/client'; | ||
|
|
||
| const DATA_PROTECTION = '/DataProtection'; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Advanced Data Protection (ADP) — capability report, MFA step-up, and the | ||
| // exemption path. | ||
| // | ||
| // The step-up window is ABSOLUTE: the server returns its expiry once and never | ||
| // slides it. Clients conceal protected values at expiry and ask again on the | ||
| // next reveal. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| export interface DataProtectionCapabilitiesData { | ||
| State: number; | ||
| StateName?: string | null; | ||
| IsProtectionEnabled: boolean; | ||
| CatalogVersion: number; | ||
| CurrentCatalogVersion: number; | ||
| PolicyEpoch: number; | ||
| StepUpWindowMinutes: number; | ||
| IsDepartmentLocked: boolean; | ||
| LockReason?: string | null; | ||
| LockProjectedEndUtc?: string | null; | ||
| } | ||
|
|
||
| export interface DataProtectionCapabilitiesResult { | ||
| Data?: DataProtectionCapabilitiesData; | ||
| } | ||
|
|
||
| export interface StepUpResult { | ||
| /** Grant id (jti) for display/audit correlation; null when grants are not configured. */ | ||
| GrantId?: string | null; | ||
| /** Signed Protected Data Grant. MEMORY ONLY — never persisted, never logged. */ | ||
| GrantToken?: string | null; | ||
| /** Absolute UTC expiry of the step-up window (ISO 8601). */ | ||
| StepUpExpiresOnUtc?: string | null; | ||
| StepUpWindowMinutes?: number; | ||
| } | ||
|
|
||
| /** Value-free ADP capability report for the caller's department. */ | ||
| export const getDataProtectionCapabilities = async (signal?: AbortSignal) => { | ||
| const response = await api.get<DataProtectionCapabilitiesResult>(`${DATA_PROTECTION}/Capabilities`, { signal }); | ||
| return response.data; | ||
| }; | ||
|
|
||
| /** | ||
| * Asks for a grant WITHOUT a second factor. | ||
| * | ||
| * A department may release named apps from the step-up prompt (ADP plan 3.3) — a dispatcher on a | ||
| * live incident cannot stop to read a code off a phone. The server answers with a grant when this | ||
| * department has exempted THIS app, and with `step_up_required` otherwise. The client never makes | ||
| * that decision; it only asks and reacts. | ||
| * | ||
| * Nothing is weakened by asking: the caller is still authenticated, and the grant that comes back | ||
| * is still tenant-bound, epoch-bound, short-lived and audited on every read it authorizes. | ||
| */ | ||
| export const requestProtectedGrant = async () => { | ||
| const response = await api.post<StepUpResult>(`${DATA_PROTECTION}/RequestGrant`, {}); | ||
| return response.data; | ||
| }; | ||
|
|
||
| /** | ||
| * Verifies the user's authenticator (TOTP) code for the ADP step-up. | ||
| * Server problem types: invalid_totp (400/401), mfa_not_enrolled (409), | ||
| * too_many_attempts (429). The code is never logged anywhere. | ||
| */ | ||
| export const verifyStepUp = async (code: string) => { | ||
| const response = await api.post<StepUpResult>(`${DATA_PROTECTION}/VerifyStepUp`, { Code: code }); | ||
| return response.data; | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'; | |||||||||||
| import { ActivityIndicator, Platform, StyleSheet, Text as RNText, TouchableOpacity, View as RNView } from 'react-native'; | ||||||||||||
| import { useSafeAreaInsets } from 'react-native-safe-area-context'; | ||||||||||||
|
|
||||||||||||
| import { StepUpPromptHost } from '@/components/data-protection/step-up-prompt-host'; | ||||||||||||
| import { DashboardViewToggles } from '@/components/dispatch-console'; | ||||||||||||
| import { NotificationButton } from '@/components/notifications/NotificationButton'; | ||||||||||||
| import { NotificationInbox } from '@/components/notifications/NotificationInbox'; | ||||||||||||
|
|
@@ -35,6 +36,7 @@ import { useLiveKitStore } from '@/stores/app/livekit-store'; | |||||||||||
| import { useCallsStore } from '@/stores/calls/store'; | ||||||||||||
| import { useChatStore } from '@/stores/chat/store'; | ||||||||||||
| import { useCheckInStore } from '@/stores/checkIn/store'; | ||||||||||||
| import { dataProtectionStore } from '@/stores/data-protection/store'; | ||||||||||||
| import { FeatureFlagKeys, featureFlagsStore } from '@/stores/feature-flags/store'; | ||||||||||||
| import useLockscreenStore from '@/stores/lockscreen/store'; | ||||||||||||
| import { useRolesStore } from '@/stores/roles/store'; | ||||||||||||
|
|
@@ -216,7 +218,7 @@ export default function TabLayout() { | |||||||||||
| context: { platform: Platform.OS }, | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| await featureFlagsStore.getState().fetchFlags(); | ||||||||||||
| await featureFlagsStore.getState().fetchFlags(), dataProtectionStore.getState().fetchCapabilities(); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Await the capability request. Line 221 awaits Proposed fix- await featureFlagsStore.getState().fetchFlags(), dataProtectionStore.getState().fetchCapabilities();
+ await Promise.all([
+ featureFlagsStore.getState().fetchFlags(),
+ dataProtectionStore.getState().fetchCapabilities(),
+ ]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| logger.info({ | ||||||||||||
| message: 'Feature flags fetched, connecting SignalR', | ||||||||||||
|
|
@@ -612,7 +614,14 @@ export default function TabLayout() { | |||||||||||
| logger.info({ | ||||||||||||
| message: 'Rendering app layout for web platform (Novu disabled)', | ||||||||||||
| }); | ||||||||||||
| return content; | ||||||||||||
| // The app's single Advanced Data Protection prompt: any screen triggers it through the store, | ||||||||||||
| // so no screen carries a modal of its own and two prompts can never stack. | ||||||||||||
| return ( | ||||||||||||
| <> | ||||||||||||
| <StepUpPromptHost /> | ||||||||||||
| {content} | ||||||||||||
| </> | ||||||||||||
| ); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return ( | ||||||||||||
|
|
@@ -621,6 +630,7 @@ export default function TabLayout() { | |||||||||||
| <NovuProvider subscriberId={`${rights?.DepartmentCode}_User_${userId}`} applicationIdentifier={config.NovuApplicationId} backendUrl={config.NovuBackendApiUrl} socketUrl={config.NovuSocketUrl}> | ||||||||||||
| {/* NotificationInbox at the root level */} | ||||||||||||
| <NotificationInbox isOpen={isNotificationsOpen} onClose={() => setIsNotificationsOpen(false)} /> | ||||||||||||
| <StepUpPromptHost /> | ||||||||||||
| {content} | ||||||||||||
| </NovuProvider> | ||||||||||||
| ) : ( | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Declare
connectedDevicefor background Bluetooth PTT.BluetoothAudioServiceNativeusesBleManagerfor background button notifications and microphone control. Android 14+ requires theconnectedDeviceforeground-service type for this interaction. RetainFOREGROUND_SERVICE_CONNECTED_DEVICE, addconnectedDevicealongsidemicrophonein the manifest and notification configuration, and remove it fromblockedPermissions.📍 Affects 1 file
app.config.ts#L102-L105(this comment)app.config.ts#L102-L105🤖 Prompt for AI Agents