diff --git a/app/src/main/assets/webui/index.html b/app/src/main/assets/webui/index.html index 6575c90..b15a64e 100644 --- a/app/src/main/assets/webui/index.html +++ b/app/src/main/assets/webui/index.html @@ -386,9 +386,8 @@ async function loadStatus() { try { const r = await fetch('/api/status', { headers: { 'X-API-Key': apiKey } }); - const d = await r.json(); - const sim = d.sims && d.sims[0]; - document.getElementById('sim-info').textContent = sim + const sim = await r.json(); + document.getElementById('sim-info').textContent = sim.slot !== undefined ? `SIM ${sim.slot + 1} - ${sim.number || sim.carrier || 'Unknown'}` : 'No SIM'; } catch (e) {} diff --git a/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt b/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt index ed4262c..4a69611 100644 --- a/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt +++ b/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt @@ -33,4 +33,10 @@ interface SmsMessageDao { @Query("SELECT COUNT(*) FROM sms_messages WHERE type = :type") suspend fun getCountByType(type: String): Int + + @Query("SELECT * FROM sms_messages WHERE simSlot = :simSlot ORDER BY timestamp DESC") + suspend fun getBySimSlot(simSlot: Int): List + + @Query("SELECT * FROM sms_messages WHERE simSlot = :simSlot AND type = :type ORDER BY timestamp DESC") + suspend fun getBySimSlotAndType(simSlot: Int, type: String): List } diff --git a/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt b/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt index ac8d828..d773480 100644 --- a/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt +++ b/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt @@ -27,17 +27,20 @@ class SmsRepository( ) } - suspend fun getAllMessages(type: String? = null): List { + suspend fun getMessagesBySimSlot(simSlot: Int, type: String? = null): List { val messages = if (type != null) { - dao.getByType(type) + dao.getBySimSlotAndType(simSlot, type) } else { - dao.getAll() + dao.getBySimSlot(simSlot) } return messages.map { it.toResponse() } } - suspend fun getMessageById(id: Long): SmsMessageResponse? { - return dao.getById(id)?.toResponse() + suspend fun getMessageById(id: Long, simSlot: Int): SmsMessageResponse? { + val message = dao.getById(id) ?: return null + // Only return if message belongs to this SIM slot + if (message.simSlot != simSlot) return null + return message.toResponse() } suspend fun insertReceivedSms(from: String, text: String, simSlot: Int): Long { diff --git a/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt b/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt index 237ea80..bf7b6dc 100644 --- a/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt +++ b/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt @@ -153,7 +153,7 @@ class TextpipeServer( webUIRoutes(context) // API routes - statusRoutes(simManager, { startTime }, { currentPort }) + statusRoutes(simManager) smsRoutes(smsRepository) } } diff --git a/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt b/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt index e4e6521..e643325 100644 --- a/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt +++ b/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt @@ -15,15 +15,12 @@ fun Application.configureAuth(simManager: SimManager) { intercept(ApplicationCallPipeline.Plugins) { val path = call.request.local.uri - // Skip auth for status endpoint - if (path == "/api/status" || path == "/") { + // Skip auth for web UI + if (path == "/" || path.startsWith("/assets") || !path.startsWith("/api/")) { return@intercept } - // Only require auth for /api/sms/* endpoints - if (!path.startsWith("/api/sms")) { - return@intercept - } + // All /api/* endpoints require auth val apiKey = extractApiKey(call) diff --git a/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt b/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt index 7e4ec69..f3ac274 100644 --- a/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt +++ b/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt @@ -9,6 +9,7 @@ import sh.sar.textpipe.data.model.MessagesResponse import sh.sar.textpipe.data.model.SendSmsRequest import sh.sar.textpipe.data.repository.SmsRepository import sh.sar.textpipe.server.auth.ApiKeyAttributeKey +import sh.sar.textpipe.server.auth.SimSlotAttributeKey fun Route.smsRoutes(smsRepository: SmsRepository) { post("/api/sms/send") { @@ -45,6 +46,12 @@ fun Route.smsRoutes(smsRepository: SmsRepository) { } get("/api/sms/messages") { + val simSlot = call.attributes.getOrNull(SimSlotAttributeKey) + if (simSlot == null) { + call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key")) + return@get + } + val type = call.request.queryParameters["type"] // Validate type if provided @@ -53,7 +60,7 @@ fun Route.smsRoutes(smsRepository: SmsRepository) { return@get } - val messages = smsRepository.getAllMessages(type) + val messages = smsRepository.getMessagesBySimSlot(simSlot, type) val response = MessagesResponse( messages = messages, total = messages.size @@ -62,6 +69,12 @@ fun Route.smsRoutes(smsRepository: SmsRepository) { } get("/api/sms/status/{id}") { + val simSlot = call.attributes.getOrNull(SimSlotAttributeKey) + if (simSlot == null) { + call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key")) + return@get + } + val idParam = call.parameters["id"] val id = idParam?.toLongOrNull() @@ -70,7 +83,7 @@ fun Route.smsRoutes(smsRepository: SmsRepository) { return@get } - val message = smsRepository.getMessageById(id) + val message = smsRepository.getMessageById(id, simSlot) if (message == null) { call.respond(HttpStatusCode.NotFound, ErrorResponse("Message not found")) return@get diff --git a/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt b/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt index f367506..983a7a3 100644 --- a/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt +++ b/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt @@ -1,19 +1,27 @@ package sh.sar.textpipe.server.routes +import io.ktor.http.* import io.ktor.server.response.* import io.ktor.server.routing.* -import sh.sar.textpipe.data.model.StatusResponse +import sh.sar.textpipe.data.model.ErrorResponse +import sh.sar.textpipe.data.model.SimInfo +import sh.sar.textpipe.server.auth.ApiKeyAttributeKey import sh.sar.textpipe.sim.SimManager -fun Route.statusRoutes(simManager: SimManager, getStartTime: () -> Long, getPort: () -> Int) { +fun Route.statusRoutes(simManager: SimManager) { get("/api/status") { - val uptime = System.currentTimeMillis() - getStartTime() - val response = StatusResponse( - running = true, - port = getPort(), - uptime = uptime, - sims = simManager.getSimInfoList() - ) - call.respond(response) + val apiKey = call.attributes.getOrNull(ApiKeyAttributeKey) + if (apiKey == null) { + call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key")) + return@get + } + + val simInfo = simManager.getSimInfoForApiKey(apiKey) + if (simInfo == null) { + call.respond(HttpStatusCode.NotFound, ErrorResponse("SIM not found")) + return@get + } + + call.respond(simInfo) } } diff --git a/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt b/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt index d6f4441..bfa5b52 100644 --- a/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt +++ b/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt @@ -112,6 +112,16 @@ class SimManager(private val context: Context) { } } + fun getSimInfoForApiKey(apiKey: String): SimInfo? { + val sim = cachedSims.find { it.apiKey == apiKey } ?: return null + return SimInfo( + slot = sim.slotIndex, + carrier = sim.carrierName, + number = sim.phoneNumber, + hasApiKey = true + ) + } + fun getCachedSims(): List = cachedSims fun regenerateApiKey(slotIndex: Int): String {