A production-grade, end-to-end Enterprise Visitor Management System built with React Native (Expo) and Firebase. This application handles the entire visitor lifecycle from pre-registration and host approval to QR-based multi-gate checkpoint verification and strict compliance auditing.
The Enterprise VMS is designed to replace legacy paper-based logbooks with a secure, digital, and automated workflow. It addresses the security and compliance needs of large corporate campuses by ensuring every entry, internal movement, and exit is cryptographically tied to a dynamic QR pass and immutably recorded.
View the full demo gallery with screenshots and videos here:
The system dynamically limits UI capabilities based on who logs in.
| Persona | Role | Primary Capabilities |
|---|---|---|
| Security Guard / Security Officer | Point of Entry | Scan QR passes, log multi-gate checkpoints, create walk-in visitors, check visitors in/out, and manually verify visitors. |
| Host (Employee) | Internal Employee | Pre-approve expected guests, approve/reject walk-in guests, and view only visits related to their own host account. |
| Receptionist / Admin | Admin / Monitor | View the live traffic dashboard, register walk-ins, check visitors in/out, and export audit logs. |
For a deeper dive into the system's inner workings, please refer to our dedicated documentation inside the docs/ folder:
- 📖 Roles & Workflows: Detailed breakdown of every user persona and step-by-step visitor lifecycles.
- 🏗️ Architecture: Explanation of the Clean Architecture implementation and Service Locator pattern.
- ⚙️ Technical Design: Firebase data schemas, Redux state management, and transaction safety mechanics.
We support both Host Pre-Approval and Walk-In flows cleanly via the RegisterWalkInVisitorUseCase.
- Host Pre-Approval Flow: An employee (Host) registers a guest in advance. The system sets the visit to
APPROVEDand dynamically generates a secureVisitorPasswith a time-limitedvalidUntilwindow. The visitor pass URL is sent through the configured notification channels. - Walk-in Flow: A guest arrives without prior notice. Security or Reception creates the visitor profile. The system sets the visit to
PENDINGand no pass is generated. The Host receives a notification. Once the Host accepts viaProcessApprovalUseCase, the pass is generated. - Photo Capture: Visitor and live check-in photos are uploaded to Firebase Storage through
IStorageService. Firestore stores the Firebase download URL, not a local devicefile://path, so the public web pass can show valid web images. If no valid image exists, the web pass falls back to a placeholder.
Because external visitors do not download the VMS application, the system generates a secure, web-based digital pass.
-
Once a visit is
APPROVED, the system creates a unique cryptographic token and generates a public URL. -
The pass document stores the same secure value in
id,qrToken, andtoken, and share actions rebuild stale or mock URLs from the real token before sending them through WhatsApp, SMS, email, or native share. -
This URL is shared with the visitor. They simply tap the link to view their dynamic QR code on their mobile browser.
-
The public pass page lives in the separate GitHub Pages project at
/Users/rajeevjoshi/Documents/GitHub/Rajeev02.github.io/public/vms/. Its lookup supports bothtokenand legacyqrTokenfields. -
Live Examples (Ready to Scan): Try clicking these to see how the web app handles each status, or scan them using the mobile app:
✅ Valid Passes
❌ Invalid / Other States 3. Expired Pass 4. Already Used/Scanned 5. Revoked Pass
Demo pass records can be refreshed with:
node seed-passes.js
- Atomic Check-In: Security scans the pass.
ValidateQrScanUseCaseensures the pass exists, is inside itsvalidFrom/validUntilwindow, and is notEXPIRED,REVOKED, orSCANNED. If valid, FirebaserunTransactionsecurely locks the DB, setting Visit toCHECKED_IN. - Checkpoints: Security at restricted areas (e.g., Server Room) scans the pass using
VerifyCheckpointUseCase. This logs the location access incheckpoint_logswithout checking the user out of the building. - Atomic Check-Out: Scanning the pass upon exit permanently sets the Visit to
COMPLETEDand the VisitorPass toEXPIRED, completely blocking pass reuse.
Dashboard data is scoped to the authenticated user:
- Security Guard, Security Officer, Receptionist, Company Admin, and Super Admin users can see operational visit data across the site.
- Host and Standard Employee users see only visits where
hostIdorcreatedBymatches their authenticated user ID. - The dashboard greeting uses the stored user name when available and falls back to email, role, or
User.
Every critical action is logged immutably via IAuditLogService into the system_audit_logs collection to satisfy SOC2/GDPR compliance. Receptionists can export this data as a CSV.
For manual testing, you can use the following Firebase Auth credentials. These route you to the appropriate UI flows based on the RBAC implementation.
| Persona | Email (Login) | Password |
|---|---|---|
| Host | host@company.com |
password123 |
| Security Guard | guard@company.com |
password123 |
| Security Officer | officer@company.com |
password123 |
| Receptionist | admin@company.com |
password123 |
| Seed Host | host@vms.com |
Password123! |
| Seed Security Guard | security@vms.com |
Password123! |
| Seed Receptionist | receptionist@vms.com |
Password123! |
| Seed Company Admin | companyadmin@vms.com |
Password123! |
| Seed Super Admin | superadmin@vms.com |
Password123! |
To create or refresh the @company.com demo users, run:
FIREBASE_WEB_API_KEY=your_firebase_web_api_key node seedCredentials.jsThe seeded @vms.com users are part of the Firebase seed data and are useful for validating role-specific dashboards.
The project adheres strictly to Clean Architecture principles.
We use a static ServiceLocator to inject external dependencies (IAuditLogService, IStorageService) into our Use Cases. This ensures the Domain is 100% pure and highly testable.
// Example: Use Case Decoupling
this.auditLogger = auditLogger || ServiceLocator.getAuditLogger();src/
├── app/ # Redux store configuration
├── core/ # Shared utilities, Logger, RBAC, ServiceLocator DI
├── domain/ # Enterprise business rules (Models, Enums, Interfaces)
├── features/ # Feature modules (Use Cases, Screens, Components)
│ ├── auth/ # Login and RBAC State
│ ├── dashboard/ # Role-scoped KPI Dashboard
│ ├── notifications/ # Email/SMS Mock facades
│ ├── qr/ # QR Scanning, Validation, and Checkpoint logic
│ ├── reports/ # CSV Generation and Audit Exports
│ └── visitor/ # Visitor Lifecycle (Approval, Check-In/Out)
├── infrastructure/ # Concrete implementations (Firebase, Mock services)
└── theme/ # Global styling and color tokens
| Category | Technology |
|---|---|
| Framework | React Native (Expo) |
| Language | TypeScript |
| State Management | Redux Toolkit |
| Database | Firebase Firestore |
| Styling | React Native Paper / StyleSheet |
| Camera/Scanner | expo-camera |
| Navigation | React Navigation |
- Node.js (v18+)
- Expo CLI (
npm install -g expo-cli) - Firebase Account and configured Project
Clone the repository and install the required dependencies:
git clone https://github.com/your-org/enterprise-vms.git
cd enterprise-vms
npm installBecause this project uses native Firebase SDKs (@react-native-firebase), you must connect it to a real Firebase project to run it locally.
- Go to the Firebase Console and create a new project.
- Enable Firestore Database (start in Test Mode for local dev).
- Enable Firebase Authentication (Email/Password provider).
- Register an Android App and an iOS App in your Firebase project.
- Download the
google-services.json(for Android) andGoogleService-Info.plist(for iOS). - Place both of these files in the absolute root directory of this repository (
/enterprise-vms/).
Firebase config files and API keys are intentionally not committed to this repository.
After cloning, add these local-only files in the project root:
google-services.json
GoogleService-Info.plist
These files are ignored by git through .gitignore, along with service-account keys:
google-services.json
GoogleService-Info.plist
config/serviceAccountKey.json
keys/
For local utility scripts, pass the Firebase Web API key through an environment variable instead of editing the script:
FIREBASE_WEB_API_KEY=your_firebase_web_api_key node seedCredentials.js
FIREBASE_WEB_API_KEY=your_firebase_web_api_key node verifyUsers.jsIf you use a different Firebase project ID, provide it as well:
FIREBASE_PROJECT_ID=your_project_id FIREBASE_WEB_API_KEY=your_firebase_web_api_key node seedCredentials.jsSince this project uses native libraries like Vision Camera, you must prebuild the app or use Expo Go if plugins allow. For the best development experience:
# Start the bundler and clear cache
npx expo start --clear
# Run on iOS simulator
npm run ios
# Run on Android emulator
npm run androidIf you run into issues while evaluating or running the project, check these common fixes:
- App crashes immediately on launch: Double-check that your
google-services.jsonandGoogleService-Info.plistfiles are correctly placed in the root directory. Firebase native will crash if these are missing. - "Firestore permission denied": Ensure your Firestore Security Rules in the Firebase console are set to allow reads/writes (Test Mode).
- "The query requires an index" ([firestore/failed-precondition]): Firestore requires composite indexes when querying multiple fields (e.g.,
statusandupdatedAt). The error in your terminal will provide a direct URL (e.g.,https://console.firebase.google.com/v1/r/project/...). Simply click that exact link; it will open your Firebase console and automatically build the required index for you (it takes about 2-3 minutes to build). - Metro Bundler Cache Issues: If you get bizarre TypeScript or module resolution errors, clear the Metro cache by running:
npx expo start -c. - Vision Camera not working in Simulator: The iOS simulator does not support hardware cameras. To test QR scanning, you must run the app on a physical device using
npx expo run:ios --device.
- Transaction Safety: Database writes for Check-In and Check-Out are locked via Firestore native transactions, preventing race conditions like double check-ins.
- QR Security: QR codes contain secure random tokens, not plain-text user data. Passes are strictly validated against
validFromandvalidUntiltimestamps and rejected for invalid states such asEXPIRED,REVOKED, andSCANNED. - Public Pass Images: Public browser passes only render HTTP/HTTPS image URLs. Invalid local device paths fall back to a placeholder image.
- Firebase Config Hygiene: Real
google-services.json,GoogleService-Info.plist, service-account keys, and API keys must stay local and must not be committed. Use environment variables such asFIREBASE_WEB_API_KEYfor local scripts. - GitGuardian Remediation: A Google API key was previously committed in Firebase config/script files. The current repository removes those tracked config files, moves script API keys to environment variables, and ignores local Firebase config files. Because the key existed in git history, it should still be rotated or restricted in Google Cloud/Firebase Console.
- Audit Trail: The
IAuditLogServicecaptures theuserIdof the security guard/receptionist performing any mutating action, ensuring non-repudiation.
The Enterprise VMS is built on a highly extensible foundation. Here are several feature flows that can be implemented next:
- Facial Recognition Check-in: Integrate a third-party SDK (like AWS Rekognition or Azure Face) to allow VIP visitors to check-in via face scan instead of a QR code.
- Push Notifications (FCM/APNs): Replace the current
MockNotificationServiceswith actual Firebase Cloud Messaging to send real-time push alerts to Hosts when their guests arrive. - Hardware Integration (Turnstiles/Printers): Connect the VMS to physical turnstiles via Bluetooth/Wi-Fi APIs, or integrate with badge printers (e.g., Brother QL series) to automatically print sticky badges upon check-in.
- Self-Service Kiosk Mode: Create a locked-down iPad/Android tablet view where walk-in visitors can register themselves, take their own photo, and automatically ping the Host for approval.
- Advanced Analytics Dashboard: Add charts and graphs (using libraries like
react-native-chart-kit) to the Receptionist dashboard to visualize peak visitor hours and checkpoint bottlenecks.
Because this project strictly adheres to Clean Architecture, replacing Firebase with your own custom backend (e.g., Node.js/Express, Spring Boot, or Go) is incredibly straightforward and requires zero changes to the UI or Use Cases.
-
Create New Data Sources: Inside
src/infrastructure/, create new files implementing the Domain interfaces. For example, createRestApiVisitDataSource.tsthat implementsIVisitDataSourceusing Axios/Fetch instead of Firestore. -
Implement API Calls:
// Example of RestApiVisitDataSource.ts export class RestApiVisitDataSource implements IVisitDataSource { async updateVisit(id: string, updates: Partial<Visit>): Promise<Visit> { const response = await axios.patch(`https://api.yourcompany.com/visits/${id}`, updates); return response.data; } // ... implement other interface methods }
-
Update the Service Locator: Open
src/core/di/ServiceLocator.tsand swap out the Firebase implementations for your new REST implementations.// Change this: // this.auditLogger = new FirestoreAuditLogService(); // To this: this.auditLogger = new RestApiAuditLogService();
-
Migrate Auth & Storage: Similarly, swap out Firebase Auth with JWT/OAuth logic in your Redux slices, and replace Firebase Storage with your own S3/Blob storage uploads in
IStorageService.
Because the entire Domain and Use Case layer only speaks to the interfaces (like IVisitDataSource), the rest of the application will seamlessly switch to your custom backend without breaking a single business rule!
