(
input: RegisterDynamicClientInput,
issuer: string | null,
)
| 764 | ); |
| 765 | |
| 766 | const decideDcrClientReuse = ( |
| 767 | input: RegisterDynamicClientInput, |
| 768 | issuer: string | null, |
| 769 | ): Effect.Effect< |
| 770 | { |
| 771 | readonly existingSlug: OAuthClientSlug | null; |
| 772 | readonly registrationSlug: OAuthClientSlug; |
| 773 | }, |
| 774 | StorageFailure |
| 775 | > => |
| 776 | Effect.gen(function* () { |
| 777 | const candidates = yield* dcrCandidatesForIssuer(input.owner, issuer); |
| 778 | const resource = input.resource ?? null; |
| 779 | if (resource !== null) { |
| 780 | const matchingResource = candidates.find((client) => client.resource === resource); |
| 781 | if (matchingResource) { |
| 782 | return { existingSlug: matchingResource.slug, registrationSlug: matchingResource.slug }; |
| 783 | } |
| 784 | const slug = dcrClientSlug(issuer, candidates.length > 0 ? resource : null, input.slug); |
| 785 | return { |
| 786 | existingSlug: null, |
| 787 | registrationSlug: slug, |
| 788 | }; |
| 789 | } |
| 790 | |
| 791 | // Resource-less request: only reuse a resource-LESS candidate. A client |
| 792 | // minted for a specific RFC 8707 resource must NOT be reused for a |
| 793 | // resource-less flow (its tokens are bound to that resource), so when only |
| 794 | // resource-scoped candidates exist we register a fresh resource-less client |
| 795 | // rather than silently borrowing one (the old `?? candidates[0]` bug). |
| 796 | const reusable = candidates.find((client) => client.resource === null); |
| 797 | if (reusable) return { existingSlug: reusable.slug, registrationSlug: reusable.slug }; |
| 798 | // Fresh resource-less client. Its slug is the bare `dcr-<host>` base, but |
| 799 | // the FIRST resource-scoped registration for an issuer also takes that base |
| 800 | // (dcrClientSlug only suffixes once candidates exist). `createClient` |
| 801 | // deletes any row with a colliding (owner, slug) first, so reusing the base |
| 802 | // here would silently clobber that resource-scoped client. Dedupe against |
| 803 | // the existing candidate slugs so the resource-less client keeps its own row. |
| 804 | const takenSlugs = new Set(candidates.map((client) => String(client.slug))); |
| 805 | const slug = uniqueDcrSlug(dcrClientSlug(issuer, null, input.slug), takenSlugs); |
| 806 | return { existingSlug: null, registrationSlug: slug }; |
| 807 | }); |
| 808 | |
| 809 | const registerDynamicClient = ( |
| 810 | input: RegisterDynamicClientInput, |
no test coverage detected