(
input: RegisterDynamicClientInput,
issuer: string | null,
)
| 717 | ); |
| 718 | |
| 719 | const decideDcrClientReuse = ( |
| 720 | input: RegisterDynamicClientInput, |
| 721 | issuer: string | null, |
| 722 | ): Effect.Effect< |
| 723 | { |
| 724 | readonly existingSlug: OAuthClientSlug | null; |
| 725 | readonly registrationSlug: OAuthClientSlug; |
| 726 | }, |
| 727 | StorageFailure |
| 728 | > => |
| 729 | Effect.gen(function* () { |
| 730 | const candidates = yield* dcrCandidatesForIssuer(input.owner, issuer); |
| 731 | const resource = input.resource ?? null; |
| 732 | if (resource !== null) { |
| 733 | const matchingResource = candidates.find((client) => client.resource === resource); |
| 734 | if (matchingResource) { |
| 735 | return { existingSlug: matchingResource.slug, registrationSlug: matchingResource.slug }; |
| 736 | } |
| 737 | const slug = dcrClientSlug(issuer, candidates.length > 0 ? resource : null, input.slug); |
| 738 | return { |
| 739 | existingSlug: null, |
| 740 | registrationSlug: slug, |
| 741 | }; |
| 742 | } |
| 743 | |
| 744 | // Resource-less request: only reuse a resource-LESS candidate. A client |
| 745 | // minted for a specific RFC 8707 resource must NOT be reused for a |
| 746 | // resource-less flow (its tokens are bound to that resource), so when only |
| 747 | // resource-scoped candidates exist we register a fresh resource-less client |
| 748 | // rather than silently borrowing one (the old `?? candidates[0]` bug). |
| 749 | const reusable = candidates.find((client) => client.resource === null); |
| 750 | if (reusable) return { existingSlug: reusable.slug, registrationSlug: reusable.slug }; |
| 751 | // Fresh resource-less client. Its slug is the bare `dcr-<host>` base, but |
| 752 | // the FIRST resource-scoped registration for an issuer also takes that base |
| 753 | // (dcrClientSlug only suffixes once candidates exist). `createClient` |
| 754 | // deletes any row with a colliding (owner, slug) first, so reusing the base |
| 755 | // here would silently clobber that resource-scoped client. Dedupe against |
| 756 | // the existing candidate slugs so the resource-less client keeps its own row. |
| 757 | const takenSlugs = new Set(candidates.map((client) => String(client.slug))); |
| 758 | const slug = uniqueDcrSlug(dcrClientSlug(issuer, null, input.slug), takenSlugs); |
| 759 | return { existingSlug: null, registrationSlug: slug }; |
| 760 | }); |
| 761 | |
| 762 | const registerDynamicClient = ( |
| 763 | input: RegisterDynamicClientInput, |
no test coverage detected