[verified] refactor: harden operations and redesign device console
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
const AUTH_PATH = /^\/api\/(?:auth(?:\/|$)|login(?:\/|$)|logout(?:\/|$))/i
|
||||
|
||||
export const DEFAULT_READ_PATHS = Object.freeze([
|
||||
'/api/health', '/api/device', '/api/sim', '/api/network', '/api/stats', '/api/connectivity',
|
||||
'/api/sms/stats', '/api/sms/list', '/api/sms/send', '/api/sms/conversation',
|
||||
'/api/data', '/api/roaming', '/api/airplane-mode', '/api/radio-mode', '/api/band-lock', '/api/cell-lock', '/api/apn', '/api/cells',
|
||||
'/api/device-network/ddns/status', '/api/device-network/ddns/config', '/api/device-network/wlan/status', '/api/device-network/wlan/profiles',
|
||||
'/api/calls', '/api/call/history', '/api/ims/status', '/api/voicemail/status',
|
||||
'/api/work-mode', '/api/esim/config', '/api/esim/lpac/status', '/api/esim/euicc', '/api/esim/profiles',
|
||||
'/api/notifications/config', '/api/notifications/logs', '/api/automation/config', '/api/automation/logs', '/api/ota/status',
|
||||
])
|
||||
|
||||
// Write access is deliberately narrower than the readable catalog. Every listed
|
||||
// operation still requires a one-use server confirmation.
|
||||
export const DEFAULT_WRITE_PATHS = Object.freeze({
|
||||
'/api/sms/send': ['POST'],
|
||||
'/api/data': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/roaming': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/airplane-mode': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/radio-mode': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/band-lock': ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
'/api/cell-lock': ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
'/api/apn': ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
'/api/device-network/ddns/config': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/device-network/wlan/profiles': ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
'/api/work-mode': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/esim/config': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/esim/profiles': ['POST', 'DELETE'],
|
||||
'/api/notifications/config': ['POST', 'PUT', 'PATCH'],
|
||||
'/api/automation/config': ['POST', 'PUT', 'PATCH'],
|
||||
})
|
||||
|
||||
export function createProxyPolicy({ readPaths = DEFAULT_READ_PATHS, writePaths = DEFAULT_WRITE_PATHS } = {}) {
|
||||
const readable = new Set(readPaths)
|
||||
const writable = new Map(Object.entries(writePaths).map(([path, methods]) => [path, new Set(methods.map(method => method.toUpperCase()))]))
|
||||
return Object.freeze({
|
||||
authorize(method, path) {
|
||||
method = String(method).toUpperCase()
|
||||
if (AUTH_PATH.test(path)) return { allowed: false, statusCode: 403, reason: 'authentication endpoints cannot be proxied' }
|
||||
if (method === 'GET' || method === 'HEAD') return readable.has(path)
|
||||
? { allowed: true, dangerous: false }
|
||||
: { allowed: false, statusCode: 403, reason: 'proxy path is not allowed' }
|
||||
if (!writable.has(path)) return readable.has(path)
|
||||
? { allowed: false, statusCode: 405, reason: 'proxy method is not allowed' }
|
||||
: { allowed: false, statusCode: 403, reason: 'proxy path is not allowed' }
|
||||
return writable.get(path).has(method)
|
||||
? { allowed: true, dangerous: true }
|
||||
: { allowed: false, statusCode: 405, reason: 'proxy method is not allowed' }
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const defaultProxyPolicy = createProxyPolicy()
|
||||
Reference in New Issue
Block a user