android-sdd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,220 @@
1
+ ---
2
+ name: api-contract
3
+ description: >
4
+ Defining and consuming API contracts in Android projects.
5
+ Load this skill when documenting API expectations, defining request/response
6
+ DTOs, handling API versioning, managing breaking changes, or aligning
7
+ the data layer with backend contracts.
8
+ ---
9
+
10
+ # API Contract
11
+
12
+ ## Overview
13
+ An API contract is the formal agreement between client and server about request/response structure, status codes, error formats, and versioning. A well-defined contract prevents runtime surprises, makes the data layer predictable, and provides a stable foundation for mocking in tests.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Define all request/response shapes as `@Serializable` DTOs — never use `Map<String, Any>`
20
+ - Document the expected status codes for every endpoint
21
+ - Version the API from day one — breaking changes require a new version
22
+ - Use `ignoreUnknownKeys = true` in the Json instance — tolerates additive API changes
23
+ - Never couple domain models to the API contract — always map through DTOs
24
+
25
+ ---
26
+
27
+ ## Contract Definition
28
+
29
+ ```kotlin
30
+ // ✅ Request DTOs — what we send
31
+ @Serializable
32
+ data class CreateUserRequest(
33
+ @SerialName("full_name") val name: String,
34
+ @SerialName("email") val email: String,
35
+ @SerialName("password") val password: String
36
+ )
37
+
38
+ @Serializable
39
+ data class UpdateUserRequest(
40
+ @SerialName("full_name") val name: String? = null,
41
+ @SerialName("email") val email: String? = null
42
+ )
43
+
44
+ // ✅ Response DTOs — what we receive
45
+ @Serializable
46
+ data class UserDto(
47
+ @SerialName("id") val id: String,
48
+ @SerialName("full_name") val name: String,
49
+ @SerialName("email") val email: String,
50
+ @SerialName("avatar_url") val avatarUrl: String? = null,
51
+ @SerialName("created_at") val createdAt: Long,
52
+ @SerialName("is_active") val isActive: Boolean = true
53
+ )
54
+
55
+ // ✅ Error response shape
56
+ @Serializable
57
+ data class ApiErrorResponse(
58
+ @SerialName("code") val code: String,
59
+ @SerialName("message") val message: String,
60
+ @SerialName("details") val details: Map<String, List<String>> = emptyMap()
61
+ )
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Endpoint Contract Documentation
67
+
68
+ ```kotlin
69
+ // ✅ Document contract as comments on the API interface
70
+ interface UserApi {
71
+
72
+ /**
73
+ * GET /users/{id}
74
+ *
75
+ * Success: 200 UserDto
76
+ * Not Found: 404 ApiErrorResponse
77
+ * Unauthorized: 401 ApiErrorResponse
78
+ */
79
+ @GET("users/{id}")
80
+ suspend fun getUser(@Path("id") id: String): UserDto
81
+
82
+ /**
83
+ * POST /users
84
+ *
85
+ * Success: 201 UserDto
86
+ * Validation Error: 422 ApiErrorResponse (details contains field errors)
87
+ * Conflict: 409 ApiErrorResponse (email already exists)
88
+ */
89
+ @POST("users")
90
+ suspend fun createUser(@Body request: CreateUserRequest): UserDto
91
+
92
+ /**
93
+ * DELETE /users/{id}
94
+ *
95
+ * Success: 204 (no body)
96
+ * Not Found: 404 ApiErrorResponse
97
+ * Forbidden: 403 ApiErrorResponse
98
+ */
99
+ @DELETE("users/{id}")
100
+ suspend fun deleteUser(@Path("id") id: String)
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## API Versioning
107
+
108
+ ```kotlin
109
+ // ✅ Version in base URL
110
+ val retrofit = Retrofit.Builder()
111
+ .baseUrl("https://api.example.com/v1/")
112
+ .build()
113
+
114
+ // ✅ Version in header (for minor versions)
115
+ class ApiVersionInterceptor : Interceptor {
116
+ override fun intercept(chain: Interceptor.Chain): Response {
117
+ val request = chain.request().newBuilder()
118
+ .addHeader("API-Version", "2024-01-01")
119
+ .build()
120
+ return chain.proceed(request)
121
+ }
122
+ }
123
+
124
+ // ✅ Separate API interfaces per major version
125
+ interface UserApiV1 {
126
+ @GET("users/{id}")
127
+ suspend fun getUser(@Path("id") id: String): UserDtoV1
128
+ }
129
+
130
+ interface UserApiV2 {
131
+ @GET("users/{id}")
132
+ suspend fun getUser(@Path("id") id: String): UserDtoV2
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Handling Additive Changes (Non-Breaking)
139
+
140
+ ```kotlin
141
+ // ✅ New optional fields — add with default value
142
+ @Serializable
143
+ data class UserDto(
144
+ @SerialName("id") val id: String,
145
+ @SerialName("name") val name: String,
146
+ // New field added by backend — safe with default
147
+ @SerialName("phone") val phone: String? = null,
148
+ @SerialName("tier") val tier: String = "free"
149
+ )
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Handling Breaking Changes
155
+
156
+ ```kotlin
157
+ // ✅ Field renamed — use @SerialName to maintain compatibility
158
+ @Serializable
159
+ data class UserDto(
160
+ // Backend renamed "username" to "full_name"
161
+ // Keep old mapping until all clients are updated
162
+ @SerialName("full_name") val name: String
163
+ )
164
+
165
+ // ✅ Type changed — custom serializer as bridge
166
+ @Serializable
167
+ data class OrderDto(
168
+ // Backend changed status from Int to String
169
+ @Serializable(with = OrderStatusSerializer::class)
170
+ @SerialName("status") val status: OrderStatus
171
+ )
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Contract Testing with Fake
177
+
178
+ ```kotlin
179
+ // ✅ Fake API implementation for tests — matches the real contract
180
+ class FakeUserApi : UserApi {
181
+ val users = mutableListOf<UserDto>()
182
+
183
+ override suspend fun getUser(id: String): UserDto {
184
+ return users.firstOrNull { it.id == id }
185
+ ?: throw HttpException(
186
+ Response.error<UserDto>(404, "".toResponseBody())
187
+ )
188
+ }
189
+
190
+ override suspend fun createUser(request: CreateUserRequest): UserDto {
191
+ val dto = UserDto(
192
+ id = UUID.randomUUID().toString(),
193
+ name = request.name,
194
+ email = request.email,
195
+ createdAt = System.currentTimeMillis()
196
+ )
197
+ users.add(dto)
198
+ return dto
199
+ }
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Anti-Patterns
206
+
207
+ - Using `Map<String, Any>` for request/response — loses type safety
208
+ - Not documenting expected status codes per endpoint
209
+ - Coupling domain models directly to API response structure
210
+ - Ignoring `@SerialName` — breaks when API uses snake_case
211
+ - No versioning strategy — every backend change becomes a breaking change
212
+
213
+ ---
214
+
215
+ ## Related Skills
216
+ - `serialization` — Kotlinx Serialization for DTO definition
217
+ - `dto-mapping` — mapping DTOs to domain models
218
+ - `rest` — HTTP conventions and status codes
219
+ - `retrofit` — implementing the API interface
220
+ - `error-handling` — mapping API errors to domain errors
@@ -0,0 +1,210 @@
1
+ ---
2
+ name: authentication
3
+ description: >
4
+ Authentication and token management for Android apps.
5
+ Load this skill when implementing login/logout flows, managing access
6
+ and refresh tokens, handling token expiry, securing token storage,
7
+ or wiring auth state to navigation.
8
+ ---
9
+
10
+ # Authentication
11
+
12
+ ## Overview
13
+ Authentication in Android involves obtaining tokens (JWT or session), storing them securely, attaching them to requests, and refreshing them before expiry. The auth state drives navigation — unauthenticated users go to the login flow, authenticated users go to the main flow.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Store tokens in **EncryptedSharedPreferences** — never plain SharedPreferences or DataStore
20
+ - Refresh the access token **proactively** before it expires — not reactively after a 401
21
+ - Handle 401 responses with a **single refresh attempt** — avoid refresh loops
22
+ - Auth state is a `StateFlow` in a singleton — all consumers observe the same source
23
+ - Logout clears all tokens and navigates to the auth graph
24
+
25
+ ---
26
+
27
+ ## Token Storage
28
+
29
+ ```kotlin
30
+ // ✅ Encrypted token storage
31
+ class TokenStorage @Inject constructor(
32
+ @ApplicationContext context: Context
33
+ ) {
34
+ private val prefs = EncryptedSharedPreferences.create(
35
+ context,
36
+ "auth_prefs",
37
+ MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
38
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
39
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
40
+ )
41
+
42
+ fun saveTokens(accessToken: String, refreshToken: String) {
43
+ prefs.edit()
44
+ .putString(KEY_ACCESS_TOKEN, accessToken)
45
+ .putString(KEY_REFRESH_TOKEN, refreshToken)
46
+ .apply()
47
+ }
48
+
49
+ fun getAccessToken(): String? = prefs.getString(KEY_ACCESS_TOKEN, null)
50
+ fun getRefreshToken(): String? = prefs.getString(KEY_REFRESH_TOKEN, null)
51
+
52
+ fun clearTokens() {
53
+ prefs.edit().clear().apply()
54
+ }
55
+
56
+ companion object {
57
+ private const val KEY_ACCESS_TOKEN = "access_token"
58
+ private const val KEY_REFRESH_TOKEN = "refresh_token"
59
+ }
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Auth State
66
+
67
+ ```kotlin
68
+ // ✅ Auth state as singleton StateFlow
69
+ sealed interface AuthState {
70
+ data object Loading : AuthState
71
+ data object Unauthenticated : AuthState
72
+ data class Authenticated(val userId: String) : AuthState
73
+ }
74
+
75
+ @Singleton
76
+ class AuthManager @Inject constructor(
77
+ private val tokenStorage: TokenStorage
78
+ ) {
79
+ private val _state = MutableStateFlow<AuthState>(AuthState.Loading)
80
+ val state: StateFlow<AuthState> = _state.asStateFlow()
81
+
82
+ init {
83
+ _state.value = if (tokenStorage.getAccessToken() != null)
84
+ AuthState.Authenticated(getUserIdFromToken())
85
+ else
86
+ AuthState.Unauthenticated
87
+ }
88
+
89
+ fun onLoginSuccess(accessToken: String, refreshToken: String, userId: String) {
90
+ tokenStorage.saveTokens(accessToken, refreshToken)
91
+ _state.value = AuthState.Authenticated(userId)
92
+ }
93
+
94
+ fun logout() {
95
+ tokenStorage.clearTokens()
96
+ _state.value = AuthState.Unauthenticated
97
+ }
98
+
99
+ private fun getUserIdFromToken(): String {
100
+ // decode JWT or read from storage
101
+ return tokenStorage.getAccessToken()?.let { JwtDecoder.getUserId(it) } ?: ""
102
+ }
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Token Refresh Interceptor
109
+
110
+ ```kotlin
111
+ // ✅ Single refresh attempt on 401
112
+ class TokenRefreshInterceptor @Inject constructor(
113
+ private val tokenStorage: TokenStorage,
114
+ private val authApi: AuthApi,
115
+ private val authManager: AuthManager
116
+ ) : Interceptor {
117
+
118
+ private val refreshLock = Mutex()
119
+
120
+ override fun intercept(chain: Interceptor.Chain): Response {
121
+ val response = chain.proceed(chain.request())
122
+
123
+ if (response.code != 401) return response
124
+
125
+ response.close()
126
+
127
+ val refreshed = runBlocking {
128
+ refreshLock.withLock {
129
+ // check if another thread already refreshed
130
+ val currentToken = tokenStorage.getAccessToken()
131
+ val requestToken = chain.request().header("Authorization")
132
+ ?.removePrefix("Bearer ")
133
+
134
+ if (currentToken != requestToken) {
135
+ // already refreshed by another call — use new token
136
+ return@withLock true
137
+ }
138
+
139
+ runCatching {
140
+ val newTokens = authApi.refreshToken(
141
+ tokenStorage.getRefreshToken() ?: return@withLock false
142
+ )
143
+ tokenStorage.saveTokens(newTokens.accessToken, newTokens.refreshToken)
144
+ true
145
+ }.getOrElse {
146
+ authManager.logout()
147
+ false
148
+ }
149
+ }
150
+ }
151
+
152
+ return if (refreshed) {
153
+ val newRequest = chain.request().newBuilder()
154
+ .header("Authorization", "Bearer ${tokenStorage.getAccessToken()}")
155
+ .build()
156
+ chain.proceed(newRequest)
157
+ } else {
158
+ chain.proceed(chain.request())
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Auth-Gated Navigation
167
+
168
+ ```kotlin
169
+ // ✅ Root NavHost reacts to auth state
170
+ @Composable
171
+ fun AppNavHost(authManager: AuthManager) {
172
+ val authState by authManager.state.collectAsStateWithLifecycle()
173
+ val navController = rememberNavController()
174
+
175
+ LaunchedEffect(authState) {
176
+ when (authState) {
177
+ is AuthState.Authenticated -> navController.navigate(MainGraph) {
178
+ popUpTo(AuthGraph) { inclusive = true }
179
+ }
180
+ is AuthState.Unauthenticated -> navController.navigate(AuthGraph) {
181
+ popUpTo(MainGraph) { inclusive = true }
182
+ }
183
+ is AuthState.Loading -> Unit
184
+ }
185
+ }
186
+
187
+ NavHost(navController = navController, startDestination = AuthGraph) {
188
+ navigation<AuthGraph>(startDestination = LoginRoute) { /* ... */ }
189
+ navigation<MainGraph>(startDestination = HomeRoute) { /* ... */ }
190
+ }
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Anti-Patterns
197
+
198
+ - Storing tokens in plain `SharedPreferences` or `DataStore` — not encrypted
199
+ - Refreshing token on every request — use proactive refresh or single-retry pattern
200
+ - Multiple simultaneous refresh calls — use `Mutex` to serialize refresh
201
+ - Keeping auth state in a ViewModel — use a singleton `AuthManager`
202
+ - Not clearing tokens on logout — stale tokens remain accessible
203
+
204
+ ---
205
+
206
+ ## Related Skills
207
+ - `okhttp` — interceptor setup for auth headers
208
+ - `encrypted-storage` — secure storage for sensitive data
209
+ - `nested-navigation` — auth vs main flow navigation graphs
210
+ - `savedstatehandle` — preserving state across auth transitions
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: certificate-pinning
3
+ description: >
4
+ SSL/TLS certificate pinning for Android to prevent MITM attacks.
5
+ Load this skill when implementing certificate pinning via OkHttp,
6
+ configuring network security config, managing pin rotation,
7
+ or handling pinning failures gracefully.
8
+ ---
9
+
10
+ # Certificate Pinning
11
+
12
+ ## Overview
13
+ Certificate pinning ensures the app only communicates with servers whose TLS certificate matches a known pinned value. This prevents man-in-the-middle attacks even if a rogue CA is trusted by the OS. Android supports pinning via OkHttp's `CertificatePinner` or the Network Security Config XML.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Always pin the **backup pin** alongside the primary — enables rotation without a forced update
20
+ - Use **public key pinning** (SPKI hash), not certificate pinning — survives certificate renewal
21
+ - Never pin in debug builds — breaks traffic inspection with Charles/Proxyman
22
+ - Have a **pin rotation plan** before shipping — expired pins = broken app
23
+ - Test pinning failures explicitly — they should fail closed, not open
24
+
25
+ ---
26
+
27
+ ## OkHttp Certificate Pinner
28
+
29
+ ```kotlin
30
+ // ✅ CertificatePinner with primary + backup pin
31
+ @Provides
32
+ @Singleton
33
+ fun provideCertificatePinner(): CertificatePinner {
34
+ return CertificatePinner.Builder()
35
+ .add(
36
+ "api.example.com",
37
+ "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // primary
38
+ "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // backup
39
+ )
40
+ .add(
41
+ "cdn.example.com",
42
+ "sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=",
43
+ "sha256/DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD="
44
+ )
45
+ .build()
46
+ }
47
+
48
+ // ✅ Attach to OkHttpClient — skip in debug
49
+ @Provides
50
+ @Singleton
51
+ fun provideOkHttpClient(
52
+ certificatePinner: CertificatePinner,
53
+ @IsDebug isDebug: Boolean
54
+ ): OkHttpClient {
55
+ return OkHttpClient.Builder()
56
+ .apply {
57
+ if (!isDebug) certificatePinner(certificatePinner)
58
+ }
59
+ .build()
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Network Security Config (XML)
66
+
67
+ ```xml
68
+ <!-- res/xml/network_security_config.xml -->
69
+ <?xml version="1.0" encoding="utf-8"?>
70
+ <network-security-config>
71
+ <domain-config>
72
+ <domain includeSubdomains="true">api.example.com</domain>
73
+ <pin-set expiration="2026-01-01">
74
+ <!-- Primary pin -->
75
+ <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
76
+ <!-- Backup pin -->
77
+ <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
78
+ </pin-set>
79
+ </domain-config>
80
+
81
+ <!-- Debug: allow cleartext and trust user CAs -->
82
+ <debug-overrides>
83
+ <trust-anchors>
84
+ <certificates src="user"/>
85
+ <certificates src="system"/>
86
+ </trust-anchors>
87
+ </debug-overrides>
88
+ </network-security-config>
89
+ ```
90
+
91
+ ```xml
92
+ <!-- AndroidManifest.xml -->
93
+ <application
94
+ android:networkSecurityConfig="@xml/network_security_config"
95
+ ...>
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Extracting the Pin
101
+
102
+ ```bash
103
+ # Extract SPKI hash from a live server
104
+ openssl s_client -connect api.example.com:443 -servername api.example.com \
105
+ </dev/null 2>/dev/null \
106
+ | openssl x509 -pubkey -noout \
107
+ | openssl pkey -pubin -outform DER \
108
+ | openssl dgst -sha256 -binary \
109
+ | openssl enc -base64
110
+
111
+ # Or from a certificate file
112
+ openssl x509 -in cert.pem -pubkey -noout \
113
+ | openssl pkey -pubin -outform DER \
114
+ | openssl dgst -sha256 -binary \
115
+ | openssl enc -base64
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Pinning Failure Handling
121
+
122
+ ```kotlin
123
+ // ✅ Catch SSLPeerUnverifiedException — pinning failure
124
+ suspend fun safeApiCall(call: suspend () -> T): Result<T> = runCatching {
125
+ call()
126
+ }.recoverCatching { throwable ->
127
+ when (throwable) {
128
+ is SSLPeerUnverifiedException -> throw CertificatePinningException(
129
+ "Certificate pinning failure — possible MITM attack"
130
+ )
131
+ else -> throw throwable
132
+ }
133
+ }
134
+
135
+ class CertificatePinningException(message: String) : SecurityException(message)
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Pin Rotation Strategy
141
+
142
+ ```
143
+ 1. Generate new certificate/key pair
144
+ 2. Extract SPKI hash of new cert → backup pin
145
+ 3. Ship app update with both old (primary) + new (backup) pins
146
+ 4. Wait for adoption (monitor crash-free rate)
147
+ 5. Deploy new certificate to server
148
+ 6. Ship next app update with new cert as primary + newer backup
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Anti-Patterns
154
+
155
+ - Pinning in debug builds — breaks all proxy-based debugging tools
156
+ - Pinning without a backup pin — one certificate renewal breaks the app
157
+ - Catching `SSLPeerUnverifiedException` silently — must fail closed, log the incident
158
+ - Using certificate pinning (full cert hash) instead of public key pinning — breaks on renewal
159
+ - Not setting an `expiration` in Network Security Config — no forced rotation reminder
160
+
161
+ ---
162
+
163
+ ## Related Skills
164
+ - `okhttp` — OkHttp client where pinner is attached
165
+ - `secure-networking` — broader TLS and network security
166
+ - `network-security-config` — XML-based network security configuration
167
+ - `certificate-transparency` — complementary server validation mechanism