The sign-up challenge is reachable from the app's own namespace
/account/challenge alone is a dead end for the iPhone app: it talks to the compat namespace, which shares the sign-up handler but had no route to ask what work was wanted. Sign-up would refuse for a missing challenge with nowhere to go and get one. Mounts the same handler under the compat prefix and lists it in the namespace index, which is what detectCapabilities reads. Refs #39 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7040dce5f7
commit
48ce3b167c
@@ -357,6 +357,13 @@ func configure(_ app: Application) async throws {
|
||||
compat.post("signup") { req in
|
||||
try await accounts.signup(req, clientIP: clientIP(req), now: unixNow())
|
||||
}
|
||||
// 🔴 The same challenge, served in the namespace the app actually asks
|
||||
// in. `/account/challenge` alone would be unreachable from a client
|
||||
// pointed at the compat prefix — it would sign up, be refused for a
|
||||
// missing challenge, and have nowhere to go and get one.
|
||||
compat.get("challenge") { req in
|
||||
try await accounts.signupChallenge(req, now: unixNow())
|
||||
}
|
||||
compat.post("signin") { req in
|
||||
try await accounts.signin(req, clientIP: clientIP(req), now: unixNow())
|
||||
}
|
||||
|
||||
@@ -783,6 +783,7 @@ struct NamespaceIndex: Content {
|
||||
"/flamenet/v1/messenger/me",
|
||||
"/flamenet/v1/messenger/users",
|
||||
"/flamenet/v1/messenger/signup",
|
||||
"/flamenet/v1/messenger/challenge",
|
||||
"/flamenet/v1/messenger/signin",
|
||||
"/flamenet/v1/messenger/session",
|
||||
"/flamenet/v1/messenger/account",
|
||||
|
||||
@@ -156,3 +156,34 @@ struct SignupProofOfWorkTests {
|
||||
#expect(!issued.challenge.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
/// The challenge has to be reachable from where the client actually asks.
|
||||
///
|
||||
/// 🔴 **`/account/challenge` alone is a dead end for the iPhone app.** It talks
|
||||
/// to the compat namespace (`/api/flamenet/v1/messenger/…`), the same handler
|
||||
/// the relay's own `/account/signup` uses. Mounting the puzzle in only one of
|
||||
/// the two places produces the worst shape available: sign-up refused for a
|
||||
/// missing challenge, and no route to go and get one.
|
||||
@Suite("the challenge is reachable")
|
||||
struct SignupChallengeReachabilityTests {
|
||||
|
||||
@Test("the compat namespace advertises the challenge beside sign-up")
|
||||
func advertised() {
|
||||
let routes = NamespaceIndex.current.routes
|
||||
#expect(routes.contains("/flamenet/v1/messenger/challenge"),
|
||||
"a client that reads the index cannot find the puzzle")
|
||||
// ⚠️ Beside sign-up, not merely present: a client that can sign up but
|
||||
// cannot ask what work is wanted is the case this whole suite is about.
|
||||
#expect(routes.contains("/flamenet/v1/messenger/signup"))
|
||||
}
|
||||
|
||||
/// ⚠️ The index is what `detectCapabilities` matches on, so a duplicate
|
||||
/// entry is not cosmetic — it is a probe that can match twice and a list
|
||||
/// that has been edited twice by two people who did not see each other.
|
||||
@Test("the index lists each route once")
|
||||
func noDuplicates() {
|
||||
let routes = NamespaceIndex.current.routes
|
||||
#expect(Set(routes).count == routes.count,
|
||||
"duplicate route in the namespace index: \(routes)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user