Start: a profile with startRequiresAppCaller refuses unauthenticated starts; set on Bifrost
Bifrost's browser binding is the secrecy of a nonce carried readable in the signed state, so only Bifrost may mint a Bifrost state. The gate sits in startOAuthFlow beside the entitlementPolicy caller check, so HTTP and the private command plane share it. Follows the red tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6d43eacd84
commit
7e17a0e952
2 changed files with 13 additions and 1 deletions
|
|
@ -45,6 +45,17 @@ export interface AppProfile {
|
|||
* plane; absent for apps with no such requirement.
|
||||
*/
|
||||
requiredEntitlementPolicyKind?: OAuthEntitlementPolicy["kind"];
|
||||
/**
|
||||
* The app binds browser completion to its own pending attempt, and the
|
||||
* binding is the secrecy of the attempt nonce, which rides readable in the
|
||||
* signed (not encrypted) state's return_to. If anyone could start a flow for
|
||||
* this app, whoever learned a victim's nonce could mint a completion bound
|
||||
* to it and sign the victim's browser in as themselves (login CSRF, finding
|
||||
* known-nonce-login-csrf). So a start for this app is refused unless the
|
||||
* caller is this app's authenticated caller: only the app can mint its own
|
||||
* state. Absent for apps whose browser starts are public by design.
|
||||
*/
|
||||
startRequiresAppCaller?: boolean;
|
||||
}
|
||||
|
||||
/** The membership signal shared by every app that gates on GameCult membership. */
|
||||
|
|
@ -142,6 +153,7 @@ const bifrostProfile: AppProfile = {
|
|||
entitlementSources: ["discord", "patreon"],
|
||||
managedConnectionProviders: [],
|
||||
allowedReturnOrigins: ["https://bifrost.gamecult.org"],
|
||||
startRequiresAppCaller: true,
|
||||
capabilities: [
|
||||
{
|
||||
key: "member_access",
|
||||
|
|
|
|||
|
|
@ -554,7 +554,7 @@ export async function startOAuthFlow(
|
|||
if (options?.trustedBrowserAttemptId && handoff.kind === "browser_completion") {
|
||||
handoff = { kind: "browser_completion", attemptId: options.trustedBrowserAttemptId };
|
||||
}
|
||||
if (entitlementPolicy && (!isAppCaller(caller) || caller.appSlug !== profile.slug)) {
|
||||
if ((entitlementPolicy || profile.startRequiresAppCaller) && (!isAppCaller(caller) || caller.appSlug !== profile.slug)) {
|
||||
return { statusCode: 401, body: { error: "app_auth_required" } };
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue