Student starter code (30% baseline)
index.html- Main HTML pagescript.js- JavaScript logicstyles.css- Styling and layoutpackage.json- Dependenciessetup.sh- Setup scriptREADME.md- Instructions (below)💡 Download the ZIP, extract it, and follow the instructions below to get started!
Protect your users' privacy with secure storage, photo metadata stripping, and consent management.
By completing this activity, you will:
expo-secure-store for hardware-encrypted storage# 1. Install dependencies
npm install --legacy-peer-deps
# 2. Start the app
npx expo start
# 3. Press 'w' for web or scan QR code with Expo Go
Note: Some features like SecureStore work differently on web vs native. For full testing, use iOS Simulator or Android Emulator.
The template includes a functional photo app with privacy UI:
| Component | File | Status |
|---|---|---|
| PhotoPrivacyService | services/PhotoPrivacyService.js |
TODO 2 |
| PrivacyAwareShare | components/PrivacyAwareShare.js |
TODO 3 |
| ConsentService | services/ConsentService.js |
TODO 4 |
| PrivacySettingsScreen | screens/PrivacySettingsScreen.js |
TODO 5 |
Implement photo metadata stripping and privacy features.
File: services/PhotoPrivacyService.js
Methods to implement:
// Strip metadata using expo-image-manipulator
static async stripMetadata(uri) {
const result = await ImageManipulator.manipulateAsync(
uri,
[], // No transformations
{ compress: 0.92, format: ImageManipulator.SaveFormat.JPEG }
);
return { success: true, uri: result.uri, metadataStripped: true };
}
// Generate random filename using expo-crypto
static async generateAnonymousFilename() {
const randomBytes = await Crypto.getRandomBytesAsync(8);
const hexString = Array.from(randomBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
return `photo_${hexString}.jpg`;
}
Success Criteria:
stripMetadata() removes EXIF data by re-encodinggenerateAnonymousFilename() creates random filenamescreateShareableCopy() combines stripping + renamingImplement the sharing workflow with privacy options.
File: components/PrivacyAwareShare.js
What to implement:
const handleShare = async () => {
// 1. Prepare photo with privacy settings
const result = await PhotoPrivacyService.createShareableCopy(
photoUri,
privacySettings
);
// 2. Check if sharing is available
const isAvailable = await Sharing.isAvailableAsync();
// 3. Share the photo
await Sharing.shareAsync(result.uri, { mimeType: 'image/jpeg' });
// 4. Clean up cache
await PhotoPrivacyService.cleanupShareCache();
};
Success Criteria:
Implement privacy consent storage and retrieval.
File: services/ConsentService.js
Methods to implement:
// Get saved consents
static async getConsents() {
const result = await SecureStorageService.getPreference(CONSENT_KEY);
if (result.success && result.value?.version === CONSENT_VERSION) {
return { ...this.defaultConsents, ...result.value.consents };
}
return null; // Needs collection
}
// Save user's consent choices
static async saveConsents(consents) {
const data = {
version: CONSENT_VERSION,
consents: { ...this.defaultConsents, ...consents, essential: true },
consentDate: new Date().toISOString()
};
return await SecureStorageService.setPreference(CONSENT_KEY, data);
}
Success Criteria:
Wire up the settings screen to use ConsentService.
File: screens/PrivacySettingsScreen.js
What to implement:
loadConsents() - Call ConsentService.getConsents()updateConsent() - Save changes immediatelyhandleDeleteAccount() - Call SecureStorageService.clearAll()Success Criteria:
| SecureStore | AsyncStorage |
|---|---|
| Hardware-encrypted | Plain text on disk |
| For: tokens, passwords | For: preferences, settings |
| Slower but secure | Faster but less secure |
| Limited size (2KB) | Larger capacity |
Photos contain hidden data that can reveal:
Add fingerprint/face unlock:
import * as LocalAuthentication from 'expo-local-authentication';
const authenticate = async () => {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (hasHardware) {
const result = await LocalAuthentication.authenticateAsync();
return result.success;
}
return false;
};
Show users what data is stored:
Temporary session with:
activity-20-privacy-implementation/
├── App.js # Main entry point
├── README.md # This file
├── package.json # Dependencies
├── services/
│ ├── SecureStorageService.js # ✅ Complete (secure/async storage)
│ ├── PhotoPrivacyService.js # ⚠️ TODO 2
│ └── ConsentService.js # ⚠️ TODO 4
├── components/
│ ├── PhotoCard.js # ✅ Complete
│ ├── ConsentBanner.js # ✅ Complete
│ └── PrivacyAwareShare.js # ⚠️ TODO 3
├── screens/
│ ├── PhotoScreen.js # ✅ Complete
│ └── PrivacySettingsScreen.js # ⚠️ TODO 5
└── utils/
└── SamplePhotos.js # ✅ Sample data
// In your component or console:
import SecureStorageService from './services/SecureStorageService';
// Test storing sensitive data
await SecureStorageService.setSecure('test_token', 'secret123');
const result = await SecureStorageService.getSecure('test_token');
console.log('Retrieved:', result.value); // Should be 'secret123'
// Clean up
await SecureStorageService.deleteSecure('test_token');
After implementing PhotoPrivacyService:
Remember: Privacy isn't just a feature-it's a promise to your users. When they trust you with their data, that trust is your most valuable asset.