From 9b6564eeddcbf927054d911c8a7a05e1ffd12d84 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 30 Jun 2026 23:57:50 +0200 Subject: [PATCH] =?UTF-8?q?Wysy=C5=82aj=20dane=20urz=C4=85dzenia=20przy=20?= =?UTF-8?q?logowaniu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kierowca/data/DeviceInfoProvider.kt | 38 +++++++++++++++++++ .../kierowca/data/DriverRepository.kt | 3 +- .../pl/firmatpp/kierowca/data/TokenStore.kt | 17 +++++++++ .../kierowca/data/model/DriverModels.kt | 15 ++++++++ tests/device-metadata.test.mjs | 27 +++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/pl/firmatpp/kierowca/data/DeviceInfoProvider.kt create mode 100644 tests/device-metadata.test.mjs diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/DeviceInfoProvider.kt b/app/src/main/java/pl/firmatpp/kierowca/data/DeviceInfoProvider.kt new file mode 100644 index 0000000..1214543 --- /dev/null +++ b/app/src/main/java/pl/firmatpp/kierowca/data/DeviceInfoProvider.kt @@ -0,0 +1,38 @@ +package pl.firmatpp.kierowca.data + +import android.content.Context +import android.os.Build +import android.provider.Settings +import pl.firmatpp.kierowca.BuildConfig +import pl.firmatpp.kierowca.data.model.DriverDeviceInfo + +class DeviceInfoProvider( + private val context: Context, + private val tokenStore: TokenStore, +) { + suspend fun currentDeviceInfo(): DriverDeviceInfo = + DriverDeviceInfo( + deviceId = tokenStore.deviceId(), + androidId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID), + brand = Build.BRAND, + manufacturer = Build.MANUFACTURER, + model = Build.MODEL, + device = Build.DEVICE, + product = Build.PRODUCT, + androidVersion = Build.VERSION.RELEASE, + sdkInt = Build.VERSION.SDK_INT, + appVersion = BuildConfig.VERSION_NAME, + serialNumber = readSerialNumber(), + ) + + private fun readSerialNumber(): String? = + runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Build.getSerial() + } else { + @Suppress("DEPRECATION") + Build.SERIAL + } + }.getOrNull() + ?.takeUnless { it.isBlank() || it.equals(Build.UNKNOWN, ignoreCase = true) } +} diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/DriverRepository.kt b/app/src/main/java/pl/firmatpp/kierowca/data/DriverRepository.kt index 081adae..169cb8b 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/DriverRepository.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/DriverRepository.kt @@ -19,12 +19,13 @@ class DriverRepository( private val context: Context, private val api: MobileDriverApi = ApiFactory.mobileDriverApi(), private val tokenStore: TokenStore = TokenStore(context), + private val deviceInfoProvider: DeviceInfoProvider = DeviceInfoProvider(context, tokenStore), ) { suspend fun requestOtp(phoneNumber: String): OtpResponse = api.requestOtp(RequestOtpBody(phoneNumber)) suspend fun verifyOtp(phoneNumber: String, code: String, deviceName: String): DriverDto { - val response = api.verifyOtp(VerifyOtpBody(phoneNumber, code, deviceName)) + val response = api.verifyOtp(VerifyOtpBody(phoneNumber, code, deviceName, deviceInfoProvider.currentDeviceInfo())) val token = response.accessToken ?: error("Brak tokenu sesji.") tokenStore.save(token) return response.driver ?: error("Brak danych kierowcy.") diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/TokenStore.kt b/app/src/main/java/pl/firmatpp/kierowca/data/TokenStore.kt index 3855356..e1b4cd3 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/TokenStore.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/TokenStore.kt @@ -6,11 +6,13 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import java.util.UUID private val Context.driverDataStore by preferencesDataStore(name = "driver_session") class TokenStore(private val context: Context) { private val tokenKey = stringPreferencesKey("access_token") + private val deviceIdKey = stringPreferencesKey("device_id") suspend fun save(token: String) { context.driverDataStore.edit { preferences -> @@ -22,6 +24,21 @@ class TokenStore(private val context: Context) { .map { it[tokenKey] } .first() + suspend fun deviceId(): String { + val existing = context.driverDataStore.data + .map { it[deviceIdKey] } + .first() + + if (!existing.isNullOrBlank()) return existing + + val generated = UUID.randomUUID().toString() + context.driverDataStore.edit { preferences -> + preferences[deviceIdKey] = generated + } + + return generated + } + suspend fun clear() { context.driverDataStore.edit { it.remove(tokenKey) } } diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt b/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt index 9b85631..c38520e 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt @@ -6,6 +6,21 @@ data class VerifyOtpBody( val phoneNumber: String, val code: String, val deviceName: String, + val device: DriverDeviceInfo, +) + +data class DriverDeviceInfo( + val deviceId: String, + val androidId: String?, + val brand: String?, + val manufacturer: String?, + val model: String?, + val device: String?, + val product: String?, + val androidVersion: String?, + val sdkInt: Int, + val appVersion: String?, + val serialNumber: String?, ) data class OtpResponse( diff --git a/tests/device-metadata.test.mjs b/tests/device-metadata.test.mjs new file mode 100644 index 0000000..12a1eb2 --- /dev/null +++ b/tests/device-metadata.test.mjs @@ -0,0 +1,27 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, '..'); + +function readSource(path) { + return readFileSync(resolve(root, path), 'utf8'); +} + +test('driver app sends Android device metadata during OTP verification', () => { + const models = readSource('app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt'); + const repository = readSource('app/src/main/java/pl/firmatpp/kierowca/data/DriverRepository.kt'); + const deviceInfo = readSource('app/src/main/java/pl/firmatpp/kierowca/data/DeviceInfoProvider.kt'); + + assert.match(models, /data class DriverDeviceInfo/); + assert.match(models, /val device: DriverDeviceInfo/); + assert.match(repository, /DeviceInfoProvider/); + assert.match(repository, /deviceInfoProvider\.currentDeviceInfo/); + assert.match(deviceInfo, /Settings\.Secure\.ANDROID_ID/); + assert.match(deviceInfo, /Build\.MANUFACTURER/); + assert.match(deviceInfo, /Build\.MODEL/); + assert.match(deviceInfo, /BuildConfig\.VERSION_NAME/); +});