mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Merge pull request #4127 from davotoula/fix/blossom-auth-standard-base64
fix(blossom): send the auth token as standard padded Base64 again
This commit is contained in:
@@ -241,7 +241,7 @@ class ImageUploadTesting {
|
||||
@Ignore("Returns invalid hash")
|
||||
fun testSovbit() =
|
||||
runBlocking {
|
||||
testBase(ServerName("sovbit", "https://cdn.sovbit.host", ServerType.Blossom))
|
||||
testBase(ServerName("sovbit", "https://files.sovbit.host", ServerType.Blossom))
|
||||
}
|
||||
|
||||
@Test()
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ val DEFAULT_MEDIA_SERVERS: List<ServerName> =
|
||||
ServerName("Azzamo", "https://blossom.azzamo.media", ServerType.Blossom),
|
||||
ServerName("YakiHonne", "https://blossom.yakihonne.com/", ServerType.Blossom),
|
||||
ServerName("Primal", "https://blossom.primal.net/", ServerType.Blossom),
|
||||
ServerName("Sovbit", "https://cdn.sovbit.host", ServerType.Blossom),
|
||||
ServerName("Sovbit", "https://files.sovbit.host", ServerType.Blossom),
|
||||
ServerName("Nostr.Download", "https://nostr.download", ServerType.Blossom),
|
||||
ServerName("Satellite (Paid)", "https://cdn.satellite.earth", ServerType.Blossom),
|
||||
ServerName("NostrMedia (Paid)", "https://nostrmedia.com", ServerType.Blossom),
|
||||
|
||||
+5
-4
@@ -40,6 +40,7 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
class BlossomReadAuthTokenProviderTest {
|
||||
private val sha = "2c5287a55cc550c9d6bc4206a4663900e083315f4a544ea3bc189e43dc330af6"
|
||||
@@ -160,8 +161,8 @@ class BlossomReadAuthTokenProviderTest {
|
||||
* End-to-end BUD-11 check on the token this path actually mints: reused
|
||||
* across every blob on the host, so it must be `server`-scoped and carry no
|
||||
* `x` tag ("When `x` tags are present, the token is only valid for
|
||||
* operations on the specified blob hashes"), and be Base64url without
|
||||
* padding.
|
||||
* operations on the specified blob hashes"), and be standard padded Base64
|
||||
* that a strict decoder accepts.
|
||||
*/
|
||||
@Test
|
||||
fun mintedTokenIsAReusableBud11GetToken() =
|
||||
@@ -170,9 +171,9 @@ class BlossomReadAuthTokenProviderTest {
|
||||
|
||||
val token =
|
||||
provider.header(host)!!.removePrefix(BlossomAuthorizationEvent.AUTH_HEADER_SCHEME)
|
||||
assertTrue("token must be base64url without padding, got: $token", token.none { it == '=' || it == '+' || it == '/' })
|
||||
|
||||
val event = BlossomAuthorizationEvent.BASE64URL.decode(token).decodeToString()
|
||||
// Strict standard decode, as deployed servers do — see BlossomAuthorizationEvent.rawToken.
|
||||
val event = Base64.decode(token).decodeToString()
|
||||
val parsed = JacksonMapper.fromJson(event) as BlossomAuthorizationEvent
|
||||
|
||||
assertEquals(BlossomAuthorizationEvent.KIND, parsed.kind)
|
||||
|
||||
+11
-14
@@ -37,22 +37,22 @@ class BlossomAuthorizationEvent(
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
/**
|
||||
* This event's JSON as Base64url without padding, per BUD-11: "the
|
||||
* authorization token MUST be encoded as Base64 URL-safe without padding
|
||||
* (Base64url, as used by JWTs)".
|
||||
* This event's JSON as standard Base64 WITH padding — the same encoder as
|
||||
* NIP-98's [com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent.rawToken].
|
||||
*
|
||||
* Deliberately NOT the same encoder as NIP-98's
|
||||
* [com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent.rawToken],
|
||||
* which stays on standard Base64 because NIP-98 does not specify a variant.
|
||||
* In practice the alphabets coincide here — a token's JSON is printable
|
||||
* ASCII, and a sextet can only reach 62/63 when the third byte of its group
|
||||
* is `>`, `~`, `?` or DEL — so the observable change is the dropped `=`.
|
||||
* NOT what BUD-11 (draft) says: "the authorization token MUST be encoded as
|
||||
* Base64 URL-safe without padding (Base64url, as used by JWTs)". Deployed
|
||||
* servers decode with a strict standard decoder (Go's base64.StdEncoding in
|
||||
* khatru-based servers), which rejects both missing padding and the `-`/`_`
|
||||
* alphabet as "invalid base64 token" — and padding is needed whenever the
|
||||
* JSON length is not a multiple of three. Interop wins over the draft. Do not
|
||||
* switch this back to `Base64.UrlSafe` without re-probing a khatru server.
|
||||
*/
|
||||
fun rawToken() = BASE64URL.encode(toJson().encodeToByteArray())
|
||||
fun rawToken() = Base64.encode(toJson().encodeToByteArray())
|
||||
|
||||
/**
|
||||
* The full `Authorization` header value for a Blossom request:
|
||||
* `Nostr <base64url-event>` (BUD-11, HTTP Authorization Header).
|
||||
* `Nostr <base64-event>` (BUD-11, HTTP Authorization Header).
|
||||
*/
|
||||
fun toAuthorizationHeader() = "$AUTH_HEADER_SCHEME${rawToken()}"
|
||||
|
||||
@@ -62,9 +62,6 @@ class BlossomAuthorizationEvent(
|
||||
/** Scheme prefix for the `Authorization` header value (BUD-11). */
|
||||
const val AUTH_HEADER_SCHEME = "Nostr "
|
||||
|
||||
/** BUD-11's required token encoding: URL-safe alphabet, no `=` padding. */
|
||||
val BASE64URL = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
|
||||
|
||||
/**
|
||||
* BUD-11 `t=get` read authorization.
|
||||
*
|
||||
|
||||
+14
-18
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nipB7Blossom
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
@@ -96,35 +97,30 @@ class BlossomAuthorizationEventTest {
|
||||
|
||||
assertTrue(header.startsWith(BlossomAuthorizationEvent.AUTH_HEADER_SCHEME))
|
||||
val token = header.removePrefix(BlossomAuthorizationEvent.AUTH_HEADER_SCHEME)
|
||||
val decoded = BlossomAuthorizationEvent.BASE64URL.decode(token).decodeToString()
|
||||
val decoded = Base64.decode(token).decodeToString()
|
||||
assertEquals(event.toJson(), decoded)
|
||||
}
|
||||
|
||||
/**
|
||||
* BUD-11: "the authorization token MUST be encoded as Base64 URL-safe
|
||||
* without padding (Base64url, as used by JWTs)". Padded standard Base64 was
|
||||
* what this produced before, so both halves are worth pinning. Several
|
||||
* lengths, because whether padding appears at all depends on the JSON
|
||||
* length mod 3 — a single sample passes by luck about half the time.
|
||||
* Must survive a strict standard decode, as deployed servers do — see
|
||||
* [BlossomAuthorizationEvent.rawToken]. The alt lengths cover every
|
||||
* `length mod 3` case, since only some of them need padding.
|
||||
*/
|
||||
@Test
|
||||
fun authorizationTokenIsBase64UrlWithoutPadding() =
|
||||
fun authorizationTokenIsStandardPaddedBase64BecauseServersDecodeStrictly() =
|
||||
runTest {
|
||||
var sawPadding = false
|
||||
listOf("a", "List", "List blobs", "List all of the blobs", "List blobs \u00e1\u00e9\u00ed")
|
||||
.forEach { alt ->
|
||||
val header = BlossomAuthorizationEvent.createListAuth(signer, alt).toAuthorizationHeader()
|
||||
val token = header.removePrefix(BlossomAuthorizationEvent.AUTH_HEADER_SCHEME)
|
||||
val event = BlossomAuthorizationEvent.createListAuth(signer, alt)
|
||||
val token = event.toAuthorizationHeader().removePrefix(BlossomAuthorizationEvent.AUTH_HEADER_SCHEME)
|
||||
|
||||
assertTrue(token.none { it == '=' }, "padding must be absent for `$alt`, got: $token")
|
||||
assertTrue(
|
||||
token.none { it == '+' || it == '/' },
|
||||
"standard-alphabet chars must not appear for `$alt`, got: $token",
|
||||
)
|
||||
assertTrue(
|
||||
token.all { it.isLetterOrDigit() || it == '-' || it == '_' },
|
||||
"token must be base64url for `$alt`, got: $token",
|
||||
)
|
||||
// kotlin.io.encoding.Base64 (default) is the strict standard decoder:
|
||||
// it throws on missing padding and on `-`/`_`, like Go's StdEncoding.
|
||||
assertEquals(event.toJson(), Base64.decode(token).decodeToString(), "strict standard decode for `$alt`")
|
||||
if (token.endsWith("=")) sawPadding = true
|
||||
}
|
||||
assertTrue(sawPadding, "at least one of these lengths needs padding; if none did, the encoder is still dropping it")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user