-
Notifications
You must be signed in to change notification settings - Fork 4.8k
fix(auth): stop accumulating authorized-client rows per app relaunch #7978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -497,6 +497,13 @@ const loadAgentAwarenessDeviceId = Effect.fn("mobile.cloud.loadAgentAwarenessDev | |
| }, | ||
| ); | ||
|
|
||
| const loadClientInstanceId = Effect.fn("mobile.cloud.loadClientInstanceId")(function* () { | ||
| const storage = yield* MobileStorage.MobileStorage; | ||
| return yield* storage.loadOrCreateClientInstanceId.pipe( | ||
| Effect.mapError(cloudEnvironmentLinkError("Could not load the client instance id.")), | ||
| ); | ||
| }); | ||
|
|
||
| const connectRelayManagedEnvironment = Effect.fn("mobile.cloud.connectRelayManagedEnvironment")( | ||
| function* (input: { | ||
| readonly clerkToken: string; | ||
|
|
@@ -557,7 +564,7 @@ const connectRelayManagedEnvironment = Effect.fn("mobile.cloud.connectRelayManag | |
| httpBaseUrl: connect.endpoint.httpBaseUrl, | ||
| credential: connect.credential, | ||
| dpopProof: bootstrapDpop, | ||
| clientMetadata: authClientMetadata(), | ||
| clientMetadata: authClientMetadata({ instanceId: yield* loadClientInstanceId() }), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High Cloud connect and refresh now fail before 🤖 Copy this AI Prompt to have your agent fix this: |
||
| }).pipe( | ||
| Effect.mapError( | ||
| cloudEnvironmentLinkError("Could not exchange a managed endpoint DPoP access token."), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,13 @@ | ||
| import type { AuthClientPresentationMetadata } from "@t3tools/contracts"; | ||
| import { Platform } from "react-native"; | ||
|
|
||
| export function authClientMetadata(): AuthClientPresentationMetadata { | ||
| export function authClientMetadata( | ||
| input: { readonly instanceId?: string } = {}, | ||
| ): AuthClientPresentationMetadata { | ||
| return { | ||
| label: "T3 Code Mobile", | ||
| deviceType: "mobile", | ||
| ...(Platform.OS === "ios" ? { os: "iOS" } : Platform.OS === "android" ? { os: "Android" } : {}), | ||
| ...(input.instanceId ? { instanceId: input.instanceId } : {}), | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ const CONNECTIONS_KEY = "t3code.connections"; | |||||
| const AGENT_AWARENESS_DEVICE_ID_KEY = "t3code.agent-awareness.device-id"; | ||||||
| const AGENT_AWARENESS_REGISTRATION_KEY = "t3code.agent-awareness.registration"; | ||||||
| const RECENT_THREAD_SHORTCUTS_KEY = "t3code.recent-thread-shortcuts"; | ||||||
| const CLIENT_INSTANCE_ID_KEY = "t3code.client-instance-id"; | ||||||
|
|
||||||
| export class MobileStorageDecodeError extends Schema.TaggedErrorClass<MobileStorageDecodeError>()( | ||||||
| "MobileStorageDecodeError", | ||||||
|
|
@@ -86,6 +87,10 @@ export class MobileStorage extends Context.Service< | |||||
| string, | ||||||
| MobileSecureStorage.MobileSecureStorageError | MobileDeviceIdGenerationError | ||||||
| >; | ||||||
| readonly loadOrCreateClientInstanceId: Effect.Effect< | ||||||
| string, | ||||||
| MobileSecureStorage.MobileSecureStorageError | MobileDeviceIdGenerationError | ||||||
| >; | ||||||
| readonly loadAgentAwarenessDeviceId: Effect.Effect< | ||||||
| string | null, | ||||||
| MobileSecureStorage.MobileSecureStorageError | ||||||
|
|
@@ -199,6 +204,19 @@ export const make = Effect.fn("MobileStorage.make")(function* () { | |||||
| return deviceId; | ||||||
| }); | ||||||
|
|
||||||
| // Stable per-install id presented during auth bootstrap so repeated | ||||||
| // connections reuse one authorized-client session per environment. | ||||||
| const loadOrCreateClientInstanceId = Effect.gen(function* () { | ||||||
| const existing = yield* secureStorage.getItem(CLIENT_INSTANCE_ID_KEY); | ||||||
| if (existing?.trim()) return existing; | ||||||
| const instanceId = yield* Effect.tryPromise({ | ||||||
| try: () => import("../lib/uuid").then(({ uuidv4 }) => uuidv4()), | ||||||
| catch: (cause) => new MobileDeviceIdGenerationError({ cause }), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Posted via Macroscope — Effect Service Conventions |
||||||
| }); | ||||||
| yield* secureStorage.setItem(CLIENT_INSTANCE_ID_KEY, instanceId); | ||||||
| return instanceId; | ||||||
| }); | ||||||
|
|
||||||
| const loadAgentAwarenessDeviceId = secureStorage | ||||||
| .getItem(AGENT_AWARENESS_DEVICE_ID_KEY) | ||||||
| .pipe(Effect.map((existing) => (existing?.trim() ? existing : null))); | ||||||
|
|
@@ -250,6 +268,7 @@ export const make = Effect.fn("MobileStorage.make")(function* () { | |||||
| saveConnection, | ||||||
| clearSavedConnection, | ||||||
| loadOrCreateAgentAwarenessDeviceId, | ||||||
| loadOrCreateClientInstanceId, | ||||||
| loadAgentAwarenessDeviceId, | ||||||
| loadAgentAwarenessRegistrationRecord, | ||||||
| saveAgentAwarenessRegistrationRecord: (record) => | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
backend/DesktopLocalEnvironmentAuth.ts:77A whitespace-only
client-instance-idfile causes a newinstanceIdto be generated on every desktop launch, so each bearer bootstrap creates a distinct authorized-client session. The persistence branch checks onlyOption.isNone(stored), so it skips writing the replacement for present-but-empty content; persist whenever the stored value is missing or blank.🤖 Copy this AI Prompt to have your agent fix this: