Security
Security measures, authentication, and data protection in WI-CARPOOL
Security Overview
WI-CARPOOL implements comprehensive security measures to protect user data and ensure safe ride-sharing experiences.
🔐 Authentication
Multi-factor authentication with JWT tokens and session management.
🛡️ Data Protection
End-to-end encryption for sensitive data and secure API communications.
🔍 Input Validation
Comprehensive input sanitization and validation on both client and server.
📊 Monitoring
Real-time security monitoring and incident response systems.
Authentication Security
Secure Token Management
// Token storage and management
class TokenManager {
private static readonly ACCESS_TOKEN_KEY = 'access_token'
private static readonly REFRESH_TOKEN_KEY = 'refresh_token'
static setTokens(accessToken: string, refreshToken: string) {
// Store in httpOnly cookies (preferred) or secure localStorage
if (this.isSecureContext()) {
document.cookie = `${this.ACCESS_TOKEN_KEY}=${accessToken}; Secure; SameSite=Strict; HttpOnly`
document.cookie = `${this.REFRESH_TOKEN_KEY}=${refreshToken}; Secure; SameSite=Strict; HttpOnly`
} else {
// Fallback to localStorage with encryption
const encryptedAccess = this.encrypt(accessToken)
const encryptedRefresh = this.encrypt(refreshToken)
localStorage.setItem(this.ACCESS_TOKEN_KEY, encryptedAccess)
localStorage.setItem(this.REFRESH_TOKEN_KEY, encryptedRefresh)
}
}
static getAccessToken(): string | null {
if (this.isSecureContext()) {
return this.getCookie(this.ACCESS_TOKEN_KEY)
}
const encrypted = localStorage.getItem(this.ACCESS_TOKEN_KEY)
return encrypted ? this.decrypt(encrypted) : null
}
static clearTokens() {
if (this.isSecureContext()) {
document.cookie = `${this.ACCESS_TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`
document.cookie = `${this.REFRESH_TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`
} else {
localStorage.removeItem(this.ACCESS_TOKEN_KEY)
localStorage.removeItem(this.REFRESH_TOKEN_KEY)
}
}
private static isSecureContext(): boolean {
return window.isSecureContext && location.protocol === 'https:'
}
private static encrypt(data: string): string {
// Implement client-side encryption
return btoa(data) // Simple base64 for demo, use proper encryption
}
private static decrypt(data: string): string {
// Implement client-side decryption
return atob(data) // Simple base64 for demo, use proper decryption
}
}
API Request Security
// Secure API client with automatic token refresh
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
class SecureApiClient {
private client: AxiosInstance
private isRefreshing = false
private failedQueue: Array<{ resolve: Function; reject: Function }> = []
constructor() {
this.client = axios.create({
baseURL: process.env.VITE_API_BASE_URL,
timeout: 30000,
withCredentials: true,
})
this.setupInterceptors()
}
private setupInterceptors() {
// Request interceptor - add auth header
this.client.interceptors.request.use(
(config) => {
const token = TokenManager.getAccessToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// Add CSRF token if available
const csrfToken = this.getCSRFToken()
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken
}
return config
},
(error) => Promise.reject(error)
)
// Response interceptor - handle token refresh
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (error.response?.status === 401 && !originalRequest._retry) {
if (this.isRefreshing) {
return new Promise((resolve, reject) => {
this.failedQueue.push({ resolve, reject })
}).then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`
return this.client(originalRequest)
})
}
originalRequest._retry = true
this.isRefreshing = true
try {
const newToken = await this.refreshToken()
this.processQueue(null, newToken)
originalRequest.headers.Authorization = `Bearer ${newToken}`
return this.client(originalRequest)
} catch (refreshError) {
this.processQueue(refreshError, null)
TokenManager.clearTokens()
window.location.href = '/signin'
return Promise.reject(refreshError)
} finally {
this.isRefreshing = false
}
}
return Promise.reject(error)
}
)
}
private getCSRFToken(): string | null {
const meta = document.querySelector('meta[name="csrf-token"]')
return meta?.getAttribute('content') || null
}
private async refreshToken(): Promise<string> {
const refreshToken = TokenManager.getRefreshToken()
if (!refreshToken) {
throw new Error('No refresh token available')
}
const response = await axios.post('/api/auth/refresh', {
refresh_token: refreshToken
})
const { access_token, refresh_token: newRefreshToken } = response.data
TokenManager.setTokens(access_token, newRefreshToken)
return access_token
}
private processQueue(error: any, token: string | null) {
this.failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error)
} else {
resolve(token)
}
})
this.failedQueue = []
}
}
Input Validation & Sanitization
Form Input Sanitization
// Input sanitization utilities
import DOMPurify from 'dompurify'
export class InputSanitizer {
static sanitizeHtml(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
ALLOW_DATA_ATTR: false,
})
}
static sanitizeText(text: string): string {
return text
.replace(/[<>"']/g, (char) => {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}
return map[char]
})
.trim()
}
static validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email) && email.length <= 254
}
static validatePhoneNumber(phone: string): boolean {
const phoneRegex = /^\+[1-9]\d{1,14}$/
return phoneRegex.test(phone)
}
static sanitizeFileName(fileName: string): string {
return fileName
.replace(/[^a-zA-Z0-9.-]/g, '_')
.substring(0, 255)
}
static validateUrl(url: string): boolean {
try {
const parsed = new URL(url)
return ['http:', 'https:'].includes(parsed.protocol)
} catch {
return false
}
}
}
// Usage in forms
function useSecureForm<T>(schema: z.ZodSchema<T>) {
const form = useForm<T>({
resolver: zodResolver(schema),
})
const secureSubmit = form.handleSubmit((data) => {
// Sanitize all string fields
const sanitizedData = Object.entries(data).reduce((acc, [key, value]) => {
if (typeof value === 'string') {
acc[key] = InputSanitizer.sanitizeText(value)
} else {
acc[key] = value
}
return acc
}, {} as any)
return onSubmit(sanitizedData)
})
return { ...form, handleSubmit: secureSubmit }
}
Content Security Policy
CSP Configuration
<!-- Content Security Policy meta tag -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://maps.googleapis.com https://js.stripe.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https: blob:;
connect-src 'self' https://api.wicarpool.com wss://api.wicarpool.com;
frame-src 'self' https://js.stripe.com;
object-src 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
">
File Upload Security
Secure File Upload
// Secure file upload component
import { useState } from 'react'
interface SecureFileUploaderProps {
onUpload: (file: File) => void
accept: string[]
maxSize: number
}
export function SecureFileUploader({ onUpload, accept, maxSize }: SecureFileUploaderProps) {
const [error, setError] = useState<string | null>(null)
const validateFile = (file: File): boolean => {
setError(null)
// Check file size
if (file.size > maxSize) {
setError(`File size must be less than ${maxSize / (1024 * 1024)}MB`)
return false
}
// Check file type
if (!accept.includes(file.type)) {
setError(`File type ${file.type} is not allowed`)
return false
}
// Check file extension
const extension = file.name.split('.').pop()?.toLowerCase()
const allowedExtensions = accept.map(type => type.split('/')[1])
if (!extension || !allowedExtensions.includes(extension)) {
setError('File extension does not match MIME type')
return false
}
// Validate file header (magic numbers)
return validateFileHeader(file)
}
const validateFileHeader = (file: File): Promise<boolean> => {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
const arrayBuffer = e.target?.result as ArrayBuffer
const uint8Array = new Uint8Array(arrayBuffer.slice(0, 8))
// Check magic numbers for common image types
const header = Array.from(uint8Array)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
const validHeaders = {
'image/jpeg': ['ffd8ffe0', 'ffd8ffe1', 'ffd8ffe2'],
'image/png': ['89504e47'],
'image/webp': ['52494646'],
}
const isValid = validHeaders[file.type as keyof typeof validHeaders]
?.some(validHeader => header.startsWith(validHeader)) ?? false
if (!isValid) {
setError('File header validation failed')
}
resolve(isValid)
}
reader.readAsArrayBuffer(file.slice(0, 8))
})
}
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
if (await validateFile(file)) {
onUpload(file)
}
}
return (
<div>
<input
type="file"
onChange={handleFileSelect}
accept={accept.join(',')}
/>
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
)
}
Security Headers
Security Headers Configuration
// Security headers for Nginx/Apache
/*
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(self), microphone=(), camera=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
*/
// Runtime security checks
export function initSecurityChecks() {
// Check if running on HTTPS in production
if (process.env.NODE_ENV === 'production' && location.protocol !== 'https:') {
console.error('Application must be served over HTTPS in production')
// Redirect to HTTPS
location.href = location.href.replace('http:', 'https:')
}
// Check for mixed content
if (location.protocol === 'https:') {
const insecureElements = document.querySelectorAll('[src^="http:"], [href^="http:"]')
if (insecureElements.length > 0) {
console.warn('Mixed content detected:', insecureElements)
}
}
// Disable right-click in production (optional)
if (process.env.NODE_ENV === 'production') {
document.addEventListener('contextmenu', (e) => {
e.preventDefault()
})
// Disable common developer shortcuts
document.addEventListener('keydown', (e) => {
if (
(e.ctrlKey && e.shiftKey && e.key === 'I') || // DevTools
(e.ctrlKey && e.shiftKey && e.key === 'J') || // Console
(e.ctrlKey && e.key === 'U') || // View Source
(e.key === 'F12') // DevTools
) {
e.preventDefault()
}
})
}
}
Security Best Practices
Authentication
- Implement multi-factor authentication for sensitive operations
- Use secure password requirements and hashing
- Implement session timeout and idle detection
- Log and monitor authentication attempts
Data Protection
- Encrypt sensitive data at rest and in transit
- Implement proper access controls and permissions
- Sanitize and validate all user inputs
- Use HTTPS for all communications
Frontend Security
- Implement Content Security Policy (CSP)
- Validate and sanitize data before rendering
- Use secure storage methods for sensitive data
- Regularly update dependencies and scan for vulnerabilities
Monitoring & Response
- Implement security event logging and monitoring
- Set up alerts for suspicious activities
- Have an incident response plan in place
- Conduct regular security audits and penetration testing