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
|
versionCode = 1
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.0"
|
||||||
|
|
||||||
// RevenueCat API key is supplied via local.properties (RC_API_KEY) and never committed.
|
// RevenueCat API key. Set RC_API_KEY in local.properties (never committed).
|
||||||
// TODO: Replace the PLACEHOLDER_RC_API_KEY fallback with a real key in local.properties before release.
|
// Debug builds fall back to a placeholder; release builds abort — see task guard below.
|
||||||
// The app will not process purchases correctly while the placeholder is active.
|
|
||||||
buildConfigField(
|
buildConfigField(
|
||||||
"String",
|
"String",
|
||||||
"RC_API_KEY",
|
"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 {
|
ksp {
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,11 @@ class SealedRevealManager @Inject constructor(
|
||||||
recipientUserId = userId
|
recipientUserId = userId
|
||||||
) ?: return null
|
) ?: 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(
|
val oneTimeKey = releaseKeyEncryptor.unwrapFromSender(
|
||||||
keyboxB64 = keybox,
|
keyboxB64 = keybox,
|
||||||
recipientPrivateKey = myPrivateKey,
|
recipientPrivateKey = myPrivateKey,
|
||||||
|
|
@ -158,7 +162,7 @@ class SealedRevealManager @Inject constructor(
|
||||||
recipientUserId = userId
|
recipientUserId = userId
|
||||||
) ?: return null
|
) ?: return null
|
||||||
|
|
||||||
val myPrivateKey = userKeyManager.getOrCreatePrivateKey()
|
val myPrivateKey = userKeyManager.loadPrivateKey() ?: return null
|
||||||
val oneTimeKey = releaseKeyEncryptor.unwrapFromSender(
|
val oneTimeKey = releaseKeyEncryptor.unwrapFromSender(
|
||||||
keyboxB64 = keybox,
|
keyboxB64 = keybox,
|
||||||
recipientPrivateKey = myPrivateKey,
|
recipientPrivateKey = myPrivateKey,
|
||||||
|
|
|
||||||
|
|
@ -391,12 +391,17 @@ service cloud.firestore {
|
||||||
&& isEncryptedAnswerPayload(request.resource.data))
|
&& 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} {
|
match /releaseKeys/{recipientId} {
|
||||||
allow read: if isCouplesMember(coupleId) && request.auth.uid == recipientId;
|
allow read: if isCouplesMember(coupleId) && request.auth.uid == recipientId;
|
||||||
allow create: if isCouplesMember(coupleId)
|
allow create: if isCouplesMember(coupleId)
|
||||||
&& request.auth.uid == userId
|
&& 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)
|
&& isKeybox(request.resource.data.encryptedAnswerKey)
|
||||||
|
&& request.resource.data.recipientUserId == recipientId
|
||||||
&& request.resource.data.keys().hasOnly(['recipientUserId', 'encryptedAnswerKey', 'releasedAt']);
|
&& request.resource.data.keys().hasOnly(['recipientUserId', 'encryptedAnswerKey', 'releasedAt']);
|
||||||
allow update, delete: if false;
|
allow update, delete: if false;
|
||||||
}
|
}
|
||||||
|
|
@ -539,6 +544,10 @@ service cloud.firestore {
|
||||||
&& request.resource.data.userId == request.auth.uid
|
&& request.resource.data.userId == request.auth.uid
|
||||||
&& request.resource.data.questionId is string
|
&& request.resource.data.questionId is string
|
||||||
&& request.resource.data.answerType 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.
|
// schemaVersion 3: partner-proof sealed answer.
|
||||||
isSealedAnswerCreate(request.resource.data)
|
isSealedAnswerCreate(request.resource.data)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue