Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ import Foundation
/// apps can persist it through ``CMUXAuthIdentityStore`` and restore the
/// identity card before the network session validates at launch.
public struct CMUXAuthUser: Codable, Equatable, Sendable {
/// The Stack Auth `clientReadOnlyMetadata` key that marks an account as a
/// demonstration-content account (the App Review demo account). Written
/// server-side only; clients can read but never set it.
public static let demonstrationContentMetadataKey = "cmuxReviewDemoContent"

/// The Stack Auth `clientReadOnlyMetadata` key that opts an account into
/// verbose diagnostics reporting (the app streams its privacy-safe
/// diagnostic events to the cmux backend). Written server-side only;
/// clients can read but never set it.
public static let verboseDiagnosticsMetadataKey = "cmuxVerboseDiagnostics"

/// The Stack Auth user id.
public let id: String
/// The user's primary email, if one is set.
Expand All @@ -14,22 +25,116 @@ public struct CMUXAuthUser: Codable, Equatable, Sendable {
public let displayName: String?
/// The user's Stack Auth profile image URL, if one is set.
public let profileImageURL: String?
/// Whether this account is server-flagged to show demonstration content
/// (a local demo computer with sample workspaces), so App Review can
/// exercise the full app without live infrastructure. Mirrored from the
/// account's `clientReadOnlyMetadata`, so it rides the same session
/// payload the app already fetches at sign-in and persists with the
/// cached identity. Defaults to `false` for every payload that predates
/// the flag.
public let demonstrationContentEnabled: Bool
/// Whether this account is server-flagged for verbose diagnostics: the
/// app batches its structured diagnostic events (bounded integer codes,
/// never terminal contents or credentials) to the cmux backend so an App
/// Review session can be reconstructed from server logs. Mirrored from
/// the account's `clientReadOnlyMetadata` exactly like
/// ``demonstrationContentEnabled`` and persisted with the cached
/// identity. Defaults to `false` for every payload that predates the
/// flag.
public let verboseDiagnosticsEnabled: Bool

/// Creates a user value.
/// - Parameters:
/// - id: The Stack Auth user id.
/// - primaryEmail: The user's primary email, if any.
/// - displayName: The user's display name, if any.
/// - profileImageURL: The user's profile image URL, if any.
/// - demonstrationContentEnabled: Whether the account is server-flagged
/// for demonstration content. Defaults to `false`.
/// - verboseDiagnosticsEnabled: Whether the account is server-flagged
/// for verbose diagnostics reporting. Defaults to `false`.
public init(
id: String,
primaryEmail: String?,
displayName: String?,
profileImageURL: String? = nil
profileImageURL: String? = nil,
demonstrationContentEnabled: Bool = false,
verboseDiagnosticsEnabled: Bool = false
) {
self.id = id
self.primaryEmail = primaryEmail
self.displayName = displayName
self.profileImageURL = profileImageURL
self.demonstrationContentEnabled = demonstrationContentEnabled
self.verboseDiagnosticsEnabled = verboseDiagnosticsEnabled
}

private enum CodingKeys: String, CodingKey {
case id
case primaryEmail
case displayName
case profileImageURL
case demonstrationContentEnabled
case verboseDiagnosticsEnabled
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.primaryEmail = try container.decodeIfPresent(String.self, forKey: .primaryEmail)
self.displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
self.profileImageURL = try container.decodeIfPresent(String.self, forKey: .profileImageURL)
// Identity cards persisted by builds that predate the flag decode as
// not demo-flagged, so a cached session can never invent the mode.
self.demonstrationContentEnabled = try container.decodeIfPresent(
Bool.self,
forKey: .demonstrationContentEnabled
) ?? false
// Same fail-closed default for the verbose-diagnostics opt-in.
self.verboseDiagnosticsEnabled = try container.decodeIfPresent(
Bool.self,
forKey: .verboseDiagnosticsEnabled
) ?? false
}

/// Resolves the demonstration-content flag from a Stack Auth
/// `clientReadOnlyMetadata` dictionary.
///
/// Only an explicit boolean `true` under
/// ``demonstrationContentMetadataKey`` activates the flag; every other
/// shape (absent key, strings, numbers, objects) resolves `false` so a
/// malformed metadata write fails closed.
/// - Parameter metadata: The raw metadata dictionary from the Stack user.
/// - Returns: Whether demonstration content is enabled for the account.
public static func demonstrationContentEnabled(
fromClientReadOnlyMetadata metadata: [String: Any]?
) -> Bool {
explicitBooleanFlag(demonstrationContentMetadataKey, in: metadata)
}

/// Resolves the verbose-diagnostics flag from a Stack Auth
/// `clientReadOnlyMetadata` dictionary, with the same fail-closed parse
/// as ``demonstrationContentEnabled(fromClientReadOnlyMetadata:)``: only
/// an explicit boolean `true` under ``verboseDiagnosticsMetadataKey``
/// activates it.
/// - Parameter metadata: The raw metadata dictionary from the Stack user.
/// - Returns: Whether verbose diagnostics are enabled for the account.
public static func verboseDiagnosticsEnabled(
fromClientReadOnlyMetadata metadata: [String: Any]?
) -> Bool {
explicitBooleanFlag(verboseDiagnosticsMetadataKey, in: metadata)
}

/// `true` only for an explicit JSON boolean `true` under `key`; every
/// other shape (absent key, strings, numbers, objects) fails closed.
private static func explicitBooleanFlag(
_ key: String,
in metadata: [String: Any]?
) -> Bool {
guard let value = metadata?[key] else { return false }
guard let number = value as? NSNumber else { return false }
// Reject non-boolean numbers (1, 2.5) so only a JSON `true` counts.
guard CFGetTypeID(number) == CFBooleanGetTypeID() else { return false }
return number.boolValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import CMUXAuthCore
import Foundation
import Testing

/// The demonstration-content activation gate: a server-written boolean on the
/// Stack account's `clientReadOnlyMetadata`, mirrored onto `CMUXAuthUser`.
/// Only an explicit JSON `true` activates it, unknown/legacy payloads fail
/// closed, and persisted identity cards from older builds decode as
/// not-flagged.
@Suite("CMUXAuthUser demonstration content")
struct CMUXAuthUserDemonstrationContentTests {
@Test("Metadata resolves only an explicit boolean true")
func metadataResolvesOnlyExplicitBooleanTrue() {
func resolve(_ metadata: [String: Any]?) -> Bool {
CMUXAuthUser.demonstrationContentEnabled(
fromClientReadOnlyMetadata: metadata
)
}

#expect(resolve(["cmuxReviewDemoContent": true]))
#expect(!resolve(["cmuxReviewDemoContent": false]))
#expect(!resolve(nil))
#expect(!resolve([:]))
#expect(!resolve(["cmuxPlan": "pro"]))
// Fail closed on every non-boolean shape a bad write could produce.
#expect(!resolve(["cmuxReviewDemoContent": "true"]))
#expect(!resolve(["cmuxReviewDemoContent": 1]))
#expect(!resolve(["cmuxReviewDemoContent": 2.5]))
#expect(!resolve(["cmuxReviewDemoContent": ["enabled": true]]))
#expect(!resolve(["cmuxReviewDemoContent": NSNull()]))
}

@Test("Metadata parsed from real JSON activates the flag")
func metadataParsedFromJSONActivates() throws {
let payload = #"{"cmuxReviewDemoContent": true, "cmuxPlan": "pro"}"#
let metadata = try JSONSerialization.jsonObject(
with: Data(payload.utf8)
) as? [String: Any]
#expect(CMUXAuthUser.demonstrationContentEnabled(
fromClientReadOnlyMetadata: metadata
))

let numericPayload = #"{"cmuxReviewDemoContent": 1}"#
let numericMetadata = try JSONSerialization.jsonObject(
with: Data(numericPayload.utf8)
) as? [String: Any]
#expect(!CMUXAuthUser.demonstrationContentEnabled(
fromClientReadOnlyMetadata: numericMetadata
))
}

@Test("Identity cards persisted before the flag decode as not flagged")
func legacyIdentityCardsDecodeAsNotFlagged() throws {
let legacy = #"{"id": "user-1", "primaryEmail": "user@example.com"}"#
let user = try JSONDecoder().decode(CMUXAuthUser.self, from: Data(legacy.utf8))
#expect(!user.demonstrationContentEnabled)
#expect(user.id == "user-1")
}

@Test("The flag round-trips through the persisted identity card")
func flagRoundTripsThroughCodable() throws {
let flagged = CMUXAuthUser(
id: "user-2",
primaryEmail: "review@example.com",
displayName: "Review",
demonstrationContentEnabled: true
)
let decoded = try JSONDecoder().decode(
CMUXAuthUser.self,
from: JSONEncoder().encode(flagged)
)
#expect(decoded == flagged)
#expect(decoded.demonstrationContentEnabled)
}

@Test("Default construction is not flagged")
func defaultConstructionIsNotFlagged() {
let user = CMUXAuthUser(id: "user-3", primaryEmail: nil, displayName: nil)
#expect(!user.demonstrationContentEnabled)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import CMUXAuthCore
import Foundation
import Testing

/// The verbose-diagnostics activation gate: a server-written boolean on the
/// Stack account's `clientReadOnlyMetadata`, mirrored onto `CMUXAuthUser`
/// exactly like the demonstration-content flag. Only an explicit JSON `true`
/// activates it, unknown/legacy payloads fail closed, and persisted identity
/// cards from older builds decode as not-flagged.
@Suite("CMUXAuthUser verbose diagnostics")
struct CMUXAuthUserVerboseDiagnosticsTests {
@Test("Metadata resolves only an explicit boolean true")
func metadataResolvesOnlyExplicitBooleanTrue() {
func resolve(_ metadata: [String: Any]?) -> Bool {
CMUXAuthUser.verboseDiagnosticsEnabled(
fromClientReadOnlyMetadata: metadata
)
}

#expect(resolve(["cmuxVerboseDiagnostics": true]))
#expect(!resolve(["cmuxVerboseDiagnostics": false]))
#expect(!resolve(nil))
#expect(!resolve([:]))
#expect(!resolve(["cmuxPlan": "pro"]))
// The sibling review flag never bleeds into this one.
#expect(!resolve(["cmuxReviewDemoContent": true]))
// Fail closed on every non-boolean shape a bad write could produce.
#expect(!resolve(["cmuxVerboseDiagnostics": "true"]))
#expect(!resolve(["cmuxVerboseDiagnostics": 1]))
#expect(!resolve(["cmuxVerboseDiagnostics": 2.5]))
#expect(!resolve(["cmuxVerboseDiagnostics": ["enabled": true]]))
#expect(!resolve(["cmuxVerboseDiagnostics": NSNull()]))
}

@Test("Metadata parsed from real JSON activates the flag")
func metadataParsedFromJSONActivates() throws {
let payload = #"{"cmuxVerboseDiagnostics": true, "cmuxPlan": "pro"}"#
let metadata = try JSONSerialization.jsonObject(
with: Data(payload.utf8)
) as? [String: Any]
#expect(CMUXAuthUser.verboseDiagnosticsEnabled(
fromClientReadOnlyMetadata: metadata
))

let numericPayload = #"{"cmuxVerboseDiagnostics": 1}"#
let numericMetadata = try JSONSerialization.jsonObject(
with: Data(numericPayload.utf8)
) as? [String: Any]
#expect(!CMUXAuthUser.verboseDiagnosticsEnabled(
fromClientReadOnlyMetadata: numericMetadata
))
}

@Test("Identity cards persisted before the flag decode as not flagged")
func legacyIdentityCardsDecodeAsNotFlagged() throws {
let legacy = #"{"id": "user-1", "primaryEmail": "user@example.com"}"#
let user = try JSONDecoder().decode(CMUXAuthUser.self, from: Data(legacy.utf8))
#expect(!user.verboseDiagnosticsEnabled)
#expect(user.id == "user-1")
}

@Test("The flag round-trips through the persisted identity card")
func flagRoundTripsThroughCodable() throws {
let flagged = CMUXAuthUser(
id: "user-2",
primaryEmail: "review@example.com",
displayName: "Review",
verboseDiagnosticsEnabled: true
)
let decoded = try JSONDecoder().decode(
CMUXAuthUser.self,
from: JSONEncoder().encode(flagged)
)
#expect(decoded == flagged)
#expect(decoded.verboseDiagnosticsEnabled)
#expect(!decoded.demonstrationContentEnabled)
}

@Test("Default construction is not flagged")
func defaultConstructionIsNotFlagged() {
let user = CMUXAuthUser(id: "user-3", primaryEmail: nil, displayName: nil)
#expect(!user.verboseDiagnosticsEnabled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,36 @@ public struct StackAuthClient: AuthClient {
let email = await user.primaryEmail
let name = await user.displayName
let profileImageURL = await user.profileImageUrl
let demonstrationContentEnabled = await user.cmuxDemonstrationContentEnabled
let verboseDiagnosticsEnabled = await user.cmuxVerboseDiagnosticsEnabled
return CMUXAuthUser(
id: id,
primaryEmail: email,
displayName: name,
profileImageURL: profileImageURL
profileImageURL: profileImageURL,
demonstrationContentEnabled: demonstrationContentEnabled,
verboseDiagnosticsEnabled: verboseDiagnosticsEnabled
)
}
}

extension CurrentUser {
/// Resolves the server-written demonstration-content flag inside the
/// user actor, so the non-`Sendable` metadata dictionary never crosses an
/// isolation boundary. See
/// ``CMUXAuthUser/demonstrationContentEnabled(fromClientReadOnlyMetadata:)``.
var cmuxDemonstrationContentEnabled: Bool {
CMUXAuthUser.demonstrationContentEnabled(
fromClientReadOnlyMetadata: clientReadOnlyMetadata
)
}

/// Resolves the server-written verbose-diagnostics flag inside the user
/// actor, mirroring ``cmuxDemonstrationContentEnabled``. See
/// ``CMUXAuthUser/verboseDiagnosticsEnabled(fromClientReadOnlyMetadata:)``.
var cmuxVerboseDiagnosticsEnabled: Bool {
CMUXAuthUser.verboseDiagnosticsEnabled(
fromClientReadOnlyMetadata: clientReadOnlyMetadata
)
}
}
Loading
Loading