()
| 1178 | // needed. The `client_secret` column is deliberately never projected. |
| 1179 | // ----------------------------------------------------------------------- |
| 1180 | const listClients = (): Effect.Effect<readonly OAuthClientSummary[], StorageFailure> => { |
| 1181 | // First-party apps lead the list: config-resolved, visible to every caller, |
| 1182 | // and projected exactly like stored rows — clientId only, never the secret. |
| 1183 | // Owner is reported as "org" (the widest visibility the summary shape can |
| 1184 | // express); the flow itself ignores owner for first-party slugs. |
| 1185 | // |
| 1186 | // `unlisted` apps are withheld here and ONLY here: listing is what offers an |
| 1187 | // app for a NEW connection, so this is the whole of "stop offering it". |
| 1188 | // `loadClient` still resolves them, keeping every existing connection's |
| 1189 | // refresh and reconnect intact. |
| 1190 | const firstPartySummaries: readonly OAuthClientSummary[] = [...firstPartyBySlug.values()] |
| 1191 | .filter((config) => config.unlisted !== true) |
| 1192 | .map((config) => ({ |
| 1193 | owner: "org", |
| 1194 | slug: firstPartyOAuthClientSlug(config.name), |
| 1195 | grant: "authorization_code", |
| 1196 | authorizationUrl: config.authorizationUrl, |
| 1197 | tokenUrl: config.tokenUrl, |
| 1198 | resource: config.resource ?? null, |
| 1199 | clientId: config.clientId, |
| 1200 | origin: { |
| 1201 | kind: "first_party", |
| 1202 | ...(config.integrations !== undefined ? { integrations: config.integrations } : {}), |
| 1203 | ...(config.allowedScopes !== undefined ? { allowedScopes: config.allowedScopes } : {}), |
| 1204 | }, |
| 1205 | })); |
| 1206 | return deps.fuma |
| 1207 | .use("oauth_client.findMany", (db) => looseDb(db).findMany("oauth_client", {})) |
| 1208 | .pipe( |
| 1209 | Effect.flatMap((rows) => |
| 1210 | Effect.forEach(rows, (row) => { |
| 1211 | const grant = parseGrant(row.grant); |
| 1212 | // EXPLICIT — a row with an unknown grant is corrupt; surface it |
| 1213 | // loudly rather than silently displaying it as authorization_code. |
| 1214 | if (grant === null) { |
| 1215 | return Effect.fail( |
| 1216 | new StorageError({ |
| 1217 | message: `oauth_client ${String(row.slug)} has an unknown grant: ${String(row.grant)}`, |
| 1218 | cause: undefined, |
| 1219 | }), |
| 1220 | ); |
| 1221 | } |
| 1222 | return Effect.succeed({ |
| 1223 | owner: String(row.owner) as Owner, |
| 1224 | slug: OAuthClientSlug.make(String(row.slug)), |
| 1225 | grant, |
| 1226 | authorizationUrl: String(row.authorization_url), |
| 1227 | tokenUrl: String(row.token_url), |
| 1228 | resource: row.resource == null ? null : String(row.resource), |
| 1229 | clientId: String(row.client_id), |
| 1230 | origin: parseOAuthClientOrigin(row), |
| 1231 | } satisfies OAuthClientSummary); |
| 1232 | }), |
| 1233 | ), |
| 1234 | Effect.map((stored) => [...firstPartySummaries, ...stored]), |
| 1235 | ); |
| 1236 | }; |
| 1237 |
nothing calls this directly
no test coverage detected