From 725a753f3543c5ffd6162ab7ba1c67f42b94d25e Mon Sep 17 00:00:00 2001 From: David Suge Date: Tue, 11 Aug 2026 12:14:03 +0100 Subject: [PATCH 1/2] feat(storm): support CDN authorization via content key and bypass key The CDN in front of Storm content bundles now requires either an x-content-key or x-bypass-key header on every request. - ContentSettings gains an optional, mutable `contentKey` field (mirrors how apiBase/apiVersion are injected from the host app), which DefaultUpdateManager attaches as `x-content-key` on the runtime bundle/update download request. - StormExtension gains an optional `bypassKey` field (masked in toString() so it never leaks into build logs), which StormPlugin attaches as `x-bypass-key` on the build-time stormDownload... task's request - that task has no running app/device to attest via Firebase AppCheck, so it needs a static shared secret instead. --- .DS_Store | Bin 0 -> 8196 bytes build.gradle | 8 ++++---- example/build.gradle | 6 +++--- gradle.properties | 5 ++++- gradle/publishing.gradle | 6 +++--- .../java/com/cube/storm/ContentSettings.java | 18 ++++++++++++++++++ .../lib/manager/DefaultUpdateManager.java | 6 ++++++ plugin/build.gradle | 6 +++--- .../cube/storm/content/StormExtension.groovy | 10 ++++++++-- .../com/cube/storm/content/StormPlugin.groovy | 4 ++++ 10 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0fcd491bf9b856732182184689569a7878f28439 GIT binary patch literal 8196 zcmeHMJ#Q015S?`##|R0zfuula8z>{vA%V~lOhrdpkcdABkCqCxajH2i@m zzaS|gR3Lr;1rjy%G`yKz>>ZyEQ6VB`rQJF2yxDnvbM|`AMC3LngAJlJB5I?v?VQH2 zps}BOr8VM=hjoajd}m{;UyQ~Tq)nItrhqA63YY?>z`vpZ_H1tLnteZbZ8QZ;f&Wqg z{(kW2Y_n*~VCAa=gB$^1o0yh{>kePQ`e?Ig%V6O_b0HKqgt~ada3LJ?KH{=y%V0w| zxp??+F|&&&6eroSz85&TELa;&0aKu=fX7sm)@g@!>9IP$pZ{<<^^J;dx0e?^%(Yj) zd^)x_yS zlX{fmbFRcPvyYtSNzPdO|`sQe9iWU@>>nt(x01C_tw zKTH0pH_bo%c5(I03(+BoPxCKE==@x2=s2V&sK$uKm3mNzIm{ta9ygo)^(^R6LCxw> zk5YVOyP+2D%EfmFy?qWg%Aaz1fA8b3`*RL6V^W9FJiZ5Aa#W=!T=wXxs?F6qFGUxH zNV#0)nuKtM)fGg=dRSLCH0xeXZDd|LsyPO7p`CQr(6zyOu48vQYLj6#e+)L z1zWN0|15+xQ{V(DuoO72@%jH&{qz45_|A;7DPRixsRFLq-Rf=v9naR4INs$w`h9e6 nj4Okc5De;v0A5f3FvPV7lw-1J%V05sW)A{H1{+L)zpB7bgG2)_ literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index 5a7b244..324107a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'com.android.tools.build:gradle:8.13.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -26,10 +26,10 @@ allprojects { maven { credentials { - username "$ANDROID_INTERNAL_PUBLISHING_USERNAME" - password "$ANDROID_INTERNAL_PUBLISHING_PASSWORD" + username "aws" + password System.env.CODEARTIFACT_AUTH_TOKEN } - url "$ANDROID_INTERNAL_PUBLISHING_URL" + url 'https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/' } } } diff --git a/example/build.gradle b/example/build.gradle index 9fea223..466b8b7 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -3,10 +3,10 @@ import com.cube.storm.content.BundleDownloadStrategy buildscript { repositories { maven { - url "$ANDROID_INTERNAL_PUBLISHING_URL" + url 'https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/' credentials { - username "$ANDROID_INTERNAL_PUBLISHING_USERNAME" - password "$ANDROID_INTERNAL_PUBLISHING_PASSWORD" + username "aws" + password System.env.CODEARTIFACT_AUTH_TOKEN } } } diff --git a/gradle.properties b/gradle.properties index 2c36212..59eead0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=3.0.0 +VERSION_NAME=3.0.1-rc1 GROUP=com.3sidedcube.storm POM_NAME=LightningContent @@ -17,3 +17,6 @@ POM_DEVELOPER_ID=scruffyfox POM_DEVELOPER_NAME=Callum Taylor android.useAndroidX=true android.enableJetifier=true + +ANDROID_INTERNAL_PUBLISHING_USERNAME=aws +ANDROID_INTERNAL_PUBLISHING_URL=https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/ \ No newline at end of file diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 6285e4f..ca5f1b5 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -31,9 +31,9 @@ allprojects { } } -ext.PUBLISHING_URL = properties.get('ANDROID_INTERNAL_PUBLISHING_URL', '') -ext.PUBLISHING_USERNAME = properties.get('ANDROID_INTERNAL_PUBLISHING_USERNAME', '') -ext.PUBLISHING_PASSWORD = properties.get('ANDROID_INTERNAL_PUBLISHING_PASSWORD', '') +ext.PUBLISHING_URL = 'https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/' +ext.PUBLISHING_USERNAME = "aws" +ext.PUBLISHING_PASSWORD = System.env.CODEARTIFACT_AUTH_TOKEN afterEvaluate { project -> publishing { diff --git a/library/src/main/java/com/cube/storm/ContentSettings.java b/library/src/main/java/com/cube/storm/ContentSettings.java index 0f52379..5e1a3d2 100755 --- a/library/src/main/java/com/cube/storm/ContentSettings.java +++ b/library/src/main/java/com/cube/storm/ContentSettings.java @@ -178,6 +178,11 @@ public static ContentSettings getInstance() */ @Getter @Setter private String authorizationToken; + /** + * Content key to use when downloading updates/bundles from the CDN + */ + @Getter @Setter @Nullable private String contentKey; + /** * The builder class for {@link com.cube.storm.ContentSettings}. Use this to create a new {@link com.cube.storm.ContentSettings} instance * with the customised properties specific for your project. @@ -324,6 +329,19 @@ public Builder authorizationToken(@NonNull String token) return this; } + /** + * Set the content key header to be used when downloading updates/bundles from the CDN + * + * @param contentKey The content key + * + * @return The {@link com.cube.storm.ContentSettings.Builder} instance for chaining + */ + public Builder contentKey(@Nullable String contentKey) + { + construct.contentKey = contentKey; + return this; + } + /** * Set the content URL to download bundles from * diff --git a/library/src/main/java/com/cube/storm/content/lib/manager/DefaultUpdateManager.java b/library/src/main/java/com/cube/storm/content/lib/manager/DefaultUpdateManager.java index f74e83b..9b16431 100755 --- a/library/src/main/java/com/cube/storm/content/lib/manager/DefaultUpdateManager.java +++ b/library/src/main/java/com/cube/storm/content/lib/manager/DefaultUpdateManager.java @@ -251,6 +251,12 @@ public void downloadUpdates(String endpoint, Observer obs .get() .header("Connection", "close"); + String contentKey = ContentSettings.getInstance().getContentKey(); + if (!TextUtils.isEmpty(contentKey)) + { + request.header("x-content-key", contentKey); + } + // Get the response Call call = redirectingHttpClient.newCall(request.build()); call.enqueue(new GZIPTarCacheConnectionInfoCallback(ContentSettings.getInstance().getStoragePath() + "/delta") diff --git a/plugin/build.gradle b/plugin/build.gradle index 70037cc..bf9ba24 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -45,9 +45,9 @@ allprojects { } } -ext.PUBLISHING_URL = properties.get('ANDROID_INTERNAL_PUBLISHING_URL', '') -ext.PUBLISHING_USERNAME = properties.get('ANDROID_INTERNAL_PUBLISHING_USERNAME', '') -ext.PUBLISHING_PASSWORD = properties.get('ANDROID_INTERNAL_PUBLISHING_PASSWORD', '') +ext.PUBLISHING_URL = 'https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/' +ext.PUBLISHING_USERNAME = "aws" +ext.PUBLISHING_PASSWORD = System.env.CODEARTIFACT_AUTH_TOKEN afterEvaluate { project -> publishing { diff --git a/plugin/src/main/groovy/com/cube/storm/content/StormExtension.groovy b/plugin/src/main/groovy/com/cube/storm/content/StormExtension.groovy index 41e82c0..7283bf6 100644 --- a/plugin/src/main/groovy/com/cube/storm/content/StormExtension.groovy +++ b/plugin/src/main/groovy/com/cube/storm/content/StormExtension.groovy @@ -9,6 +9,11 @@ class StormExtension { String bundleEnvironment = "" String authUsername = "" String authPassword = "" + /** + * Static key sent as the x-bypass-key header on the build-time bundle download request, for + * CDN edge gating that isn't tied to a running app (so Firebase AppCheck doesn't apply here). + */ + String bypassKey = "" Date bundleTimestamp = null Boolean obeyLandmark = null @@ -48,7 +53,8 @@ class StormExtension { obeyLandmark: this.obeyLandmark == null ? other.obeyLandmark : this.obeyLandmark, bundleDownloadStrategy: this.bundleDownloadStrategy == null ? other.bundleDownloadStrategy : this.bundleDownloadStrategy, authUsername: this.authUsername.isEmpty() ? other.authUsername : this.authUsername, - authPassword: this.authPassword.isEmpty() ? other.authPassword : this.authPassword + authPassword: this.authPassword.isEmpty() ? other.authPassword : this.authPassword, + bypassKey: this.bypassKey.isEmpty() ? other.bypassKey : this.bypassKey ) } @@ -58,6 +64,6 @@ class StormExtension { public String toString() { - return "Storm(apiBase=${apiBase}, apiVersion=${apiVersion}, appId=${appId}, orgId=${orgId}, orgName=${orgName}, bundleEnv=${bundleEnvironment}, bundleTimestamp=${bundleTimestamp}, obeyLandmark=${obeyLandmark}, bundleDownloadStrategy=${bundleDownloadStrategy}, authUsername=${authUsername}, authPassword=${authPassword}, url=${url})" + return "Storm(apiBase=${apiBase}, apiVersion=${apiVersion}, appId=${appId}, orgId=${orgId}, orgName=${orgName}, bundleEnv=${bundleEnvironment}, bundleTimestamp=${bundleTimestamp}, obeyLandmark=${obeyLandmark}, bundleDownloadStrategy=${bundleDownloadStrategy}, authUsername=${authUsername}, authPassword=${authPassword}, bypassKey=${bypassKey.isEmpty() ? "" : "***"}, url=${url})" } } diff --git a/plugin/src/main/groovy/com/cube/storm/content/StormPlugin.groovy b/plugin/src/main/groovy/com/cube/storm/content/StormPlugin.groovy index a7392f6..24021ec 100644 --- a/plugin/src/main/groovy/com/cube/storm/content/StormPlugin.groovy +++ b/plugin/src/main/groovy/com/cube/storm/content/StormPlugin.groovy @@ -122,6 +122,7 @@ class StormPlugin implements Plugin { boolean requiresAuth = mergedStormConfig.requiresAuth() String url = mergedStormConfig.url String authUsername = mergedStormConfig.authUsername + String bypassKey = mergedStormConfig.bypassKey String sanitisedUrl = url.replaceAll("[\\\\/:*?\"<>|]", "_") def archiveFile = project.layout.buildDirectory.file("storm/${sanitisedUrl}/bundle.tar.gz") @@ -173,6 +174,9 @@ class StormPlugin implements Plugin { println "Setting auth token for user ${authUsername}: ${authToken}" downloadTaskRef.header "Authorization", authToken } + if (!bypassKey.isEmpty()) { + downloadTaskRef.header "x-bypass-key", bypassKey + } } src url dest archiveFile.get().asFile From 514e4707109768fcfee74b2938ead69c9fa7e285 Mon Sep 17 00:00:00 2001 From: David Suge Date: Tue, 11 Aug 2026 15:36:59 +0100 Subject: [PATCH 2/2] chore(plugin/build.gradle): bump the VERSION_NAME to 2.0.1-rc2 --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 2 ++ gradle.properties | 7 ++----- plugin/build.gradle | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 0fcd491bf9b856732182184689569a7878f28439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMJ#Q015S?`##|R0zfuula8z>{vA%V~lOhrdpkcdABkCqCxajH2i@m zzaS|gR3Lr;1rjy%G`yKz>>ZyEQ6VB`rQJF2yxDnvbM|`AMC3LngAJlJB5I?v?VQH2 zps}BOr8VM=hjoajd}m{;UyQ~Tq)nItrhqA63YY?>z`vpZ_H1tLnteZbZ8QZ;f&Wqg z{(kW2Y_n*~VCAa=gB$^1o0yh{>kePQ`e?Ig%V6O_b0HKqgt~ada3LJ?KH{=y%V0w| zxp??+F|&&&6eroSz85&TELa;&0aKu=fX7sm)@g@!>9IP$pZ{<<^^J;dx0e?^%(Yj) zd^)x_yS zlX{fmbFRcPvyYtSNzPdO|`sQe9iWU@>>nt(x01C_tw zKTH0pH_bo%c5(I03(+BoPxCKE==@x2=s2V&sK$uKm3mNzIm{ta9ygo)^(^R6LCxw> zk5YVOyP+2D%EfmFy?qWg%Aaz1fA8b3`*RL6V^W9FJiZ5Aa#W=!T=wXxs?F6qFGUxH zNV#0)nuKtM)fGg=dRSLCH0xeXZDd|LsyPO7p`CQr(6zyOu48vQYLj6#e+)L z1zWN0|15+xQ{V(DuoO72@%jH&{qz45_|A;7DPRixsRFLq-Rf=v9naR4INs$w`h9e6 nj4Okc5De;v0A5f3FvPV7lw-1J%V05sW)A{H1{+L)zpB7bgG2)_ diff --git a/.gitignore b/.gitignore index d416fa3..0553702 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.iml local.properties build +.DS_Store +**/.DS_Store # Created by https://www.gitignore.io ### Android ### diff --git a/gradle.properties b/gradle.properties index 59eead0..b963d8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=3.0.1-rc1 +VERSION_NAME=3.0.1-rc2 GROUP=com.3sidedcube.storm POM_NAME=LightningContent @@ -16,7 +16,4 @@ POM_LICENSE_DIST=repo POM_DEVELOPER_ID=scruffyfox POM_DEVELOPER_NAME=Callum Taylor android.useAndroidX=true -android.enableJetifier=true - -ANDROID_INTERNAL_PUBLISHING_USERNAME=aws -ANDROID_INTERNAL_PUBLISHING_URL=https://threesidedcube-android-735275294014.d.codeartifact.us-east-1.amazonaws.com/maven/3SidedCube-Android/ \ No newline at end of file +android.enableJetifier=true \ No newline at end of file diff --git a/plugin/build.gradle b/plugin/build.gradle index bf9ba24..ce83185 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -28,8 +28,8 @@ dependencies { POM_NAME='LightningContentGradlePlugin' POM_ARTIFACT_ID='content-gradle-plugin' POM_PACKAGING='jar' -// 2.0.0: migrated to the AndroidComponents variant API. Requires AGP 7.2+ (supports AGP 9.x). -VERSION_NAME="2.0.0" +// 2.0.1: adds StormExtension.bypassKey, sent as the x-bypass-key header on the build-time bundle download request. +VERSION_NAME="2.0.1-rc2" java { sourceCompatibility = JavaVersion.VERSION_17