fix: add Tink dependency, release key cleanup, rules hardening (batch v1.0.17)
This commit is contained in:
parent
84eab1825b
commit
8de5990230
|
|
@ -19,13 +19,12 @@ android {
|
|||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
|
||||
// RevenueCat API key is supplied via local.properties (RC_API_KEY) and never committed.
|
||||
// TODO: Replace the PLACEHOLDER_RC_API_KEY fallback with a real key in local.properties before release.
|
||||
// The app will not process purchases correctly while the placeholder is active.
|
||||
// RevenueCat API key. Set RC_API_KEY in local.properties (never committed).
|
||||
// Debug builds fall back to a placeholder; release builds abort — see task guard below.
|
||||
buildConfigField(
|
||||
"String",
|
||||
"RC_API_KEY",
|
||||
"\"${properties["RC_API_KEY"]?.toString() ?: "PLACEHOLDER_RC_API_KEY"}\""
|
||||
"\"${properties["RC_API_KEY"]?.toString() ?: System.getenv("RC_API_KEY") ?: "PLACEHOLDER_RC_API_KEY"}\""
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +66,22 @@ android {
|
|||
|
||||
}
|
||||
|
||||
// Abort any release assemble/bundle task when RC_API_KEY is absent or is the placeholder.
|
||||
// This runs at execution time so debug builds are never affected.
|
||||
tasks.matching { it.name.let { n ->
|
||||
(n.startsWith("assemble") || n.startsWith("bundle")) && n.contains("Release", ignoreCase = true)
|
||||
}}.configureEach {
|
||||
doFirst {
|
||||
val key = (findProperty("RC_API_KEY") as? String)?.takeIf { it.isNotBlank() }
|
||||
?: System.getenv("RC_API_KEY")?.takeIf { it.isNotBlank() }
|
||||
if (key == null || key == "PLACEHOLDER_RC_API_KEY") {
|
||||
throw GradleException(
|
||||
"RC_API_KEY is not set. Add it to local.properties or export RC_API_KEY before running a release build."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,11 @@ class SealedRevealManager @Inject constructor(
|
|||
recipientUserId = userId
|
||||
) ?: return null
|
||||
|
||||
val myPrivateKey = userKeyManager.getOrCreatePrivateKey()
|
||||
// loadPrivateKey — NOT getOrCreatePrivateKey. On a second device there is no local
|
||||
// private key; generating a fresh one here would mismatch the published public key that
|
||||
// the partner encrypted to, making unwrap fail silently or produce garbage. Returning
|
||||
// null lets the caller surface LOST_LOCAL_KEY / WAITING_FOR_PARTNER correctly.
|
||||
val myPrivateKey = userKeyManager.loadPrivateKey() ?: return null
|
||||
val oneTimeKey = releaseKeyEncryptor.unwrapFromSender(
|
||||
keyboxB64 = keybox,
|
||||
recipientPrivateKey = myPrivateKey,
|
||||
|
|
@ -158,7 +162,7 @@ class SealedRevealManager @Inject constructor(
|
|||
recipientUserId = userId
|
||||
) ?: return null
|
||||
|
||||
val myPrivateKey = userKeyManager.getOrCreatePrivateKey()
|
||||
val myPrivateKey = userKeyManager.loadPrivateKey() ?: return null
|
||||
val oneTimeKey = releaseKeyEncryptor.unwrapFromSender(
|
||||
keyboxB64 = keybox,
|
||||
recipientPrivateKey = myPrivateKey,
|
||||
|
|
|
|||
|
|
@ -391,12 +391,17 @@ service cloud.firestore {
|
|||
&& isEncryptedAnswerPayload(request.resource.data))
|
||||
);
|
||||
|
||||
// One-time key release for sealed thread answers (same pattern as daily answer release keys).
|
||||
// One-time key release for sealed thread answers (same guards as daily answer release keys).
|
||||
match /releaseKeys/{recipientId} {
|
||||
allow read: if isCouplesMember(coupleId) && request.auth.uid == recipientId;
|
||||
allow create: if isCouplesMember(coupleId)
|
||||
&& request.auth.uid == userId
|
||||
&& recipientId != userId
|
||||
&& recipientId in get(/databases/$(database)/documents/couples/$(coupleId)).data.userIds
|
||||
// Both answers must exist before either key can be released — prevents early single-sided release.
|
||||
&& exists(/databases/$(database)/documents/couples/$(coupleId)/question_threads/$(threadId)/answers/$(recipientId))
|
||||
&& isKeybox(request.resource.data.encryptedAnswerKey)
|
||||
&& request.resource.data.recipientUserId == recipientId
|
||||
&& request.resource.data.keys().hasOnly(['recipientUserId', 'encryptedAnswerKey', 'releasedAt']);
|
||||
allow update, delete: if false;
|
||||
}
|
||||
|
|
@ -539,6 +544,10 @@ service cloud.firestore {
|
|||
&& request.resource.data.userId == request.auth.uid
|
||||
&& request.resource.data.questionId is string
|
||||
&& request.resource.data.answerType is string
|
||||
// answerDate must match the path segment — prevents a client writing a doc
|
||||
// whose metadata disagrees with the path it lands in.
|
||||
&& request.resource.data.answerDate is string
|
||||
&& request.resource.data.answerDate == date
|
||||
&& (
|
||||
// schemaVersion 3: partner-proof sealed answer.
|
||||
isSealedAnswerCreate(request.resource.data)
|
||||
|
|
|
|||
Loading…
Reference in New Issue