1import * as identitystore from "@distilled.cloud/aws/identitystore";
2import * as ssoAdmin from "@distilled.cloud/aws/sso-admin";
3import * as Effect from "effect/Effect";
4import * as Redacted from "effect/Redacted";
5import * as Schedule from "effect/Schedule";
6import * as Stream from "effect/Stream";
7
8/**
9 * Unwrap an identity-store sensitive field. Distilled decodes
10 * `smithy.api#sensitive` strings (`DisplayName`, `Description`, `UserName`,
11 * …) into `Redacted.Redacted<string>` at runtime, so any resource-level
12 * comparison or Attributes projection must unwrap first — comparing the
13 * Redacted wrapper against a plain string is always `false` and would make
14 * reconcile loop or persist `<redacted>` wrappers into state.
15 */
16export const unredact = (
17 value: string | Redacted.Redacted<string> | undefined,
18): string | undefined =>
19 value === undefined
20 ? undefined
21 : Redacted.isRedacted(value)
22 ? Redacted.value(value)
23 : value;
24
25export const retryIdentityCenter = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
26 effect.pipe(
27 Effect.retry({
28 while: (error: any) =>
29 error?._tag === "ConflictException" ||
30 error?._tag === "ThrottlingException" ||
31 error?._tag === "InternalServerException",
32 schedule: Schedule.max([Schedule.exponential(200), Schedule.recurs(8)]),
33 }),
34 );
35
36export const listInstances = Effect.fn(function* () {
37 return yield* ssoAdmin.listInstances
38 .items({
39 MaxResults: 100,
40 })
41 .pipe(
42 Stream.runCollect,
43 Effect.map(
44 (instances) => Array.from(instances) as ssoAdmin.InstanceMetadata[],
45 ),
46 );
47});
48
49export const resolveInstance = Effect.fn(function* (instanceArn?: string) {
50 const instances = yield* listInstances();
51
52 if (instanceArn) {
53 const selected = instances.find(
54 (instance) => instance.InstanceArn === instanceArn,
55 );
56 if (!selected?.InstanceArn || !selected.IdentityStoreId) {
57 return yield* Effect.fail(
58 new Error(`Identity Center instance '${instanceArn}' was not found`),
59 );
60 }
61 return selected;
62 }
63
64 if (instances.length === 1) {
65 return instances[0]!;
66 }
67
68 const active = instances.filter((instance) => instance.Status === "ACTIVE");
69 if (active.length === 1) {
70 return active[0]!;
71 }
72
73 return yield* Effect.fail(
74 new Error(
75 "Unable to resolve a single visible Identity Center instance; pass instanceArn explicitly",
76 ),
77 );
78});
79
80export const resolveIdentityStoreId = Effect.fn(function* ({
81 identityStoreId,
82 instanceArn,
83}: {
84 identityStoreId?: string;
85 instanceArn?: string;
86}) {
87 if (identityStoreId) {
88 return identityStoreId;
89 }
90
91 const instance = yield* resolveInstance(instanceArn);
92 if (!instance.IdentityStoreId) {
93 return yield* Effect.fail(
94 new Error(
95 `Identity Center instance '${instance.InstanceArn}' is missing an identity store ID`,
96 ),
97 );
98 }
99
100 return instance.IdentityStoreId;
101});
102
103export const toInstanceAttributes = (
104 instance: ssoAdmin.InstanceMetadata,
105): {
106 instanceArn: string;
107 identityStoreId: string;
108 ownerAccountId: string | undefined;
109 name: string | undefined;
110 status: string | undefined;
111 statusReason: string | undefined;
112 createdDate: Date | undefined;
113} => ({
114 instanceArn: instance.InstanceArn ?? "",
115 identityStoreId: instance.IdentityStoreId ?? "",
116 ownerAccountId: instance.OwnerAccountId,
117 name: instance.Name,
118 status: instance.Status,
119 statusReason: instance.StatusReason,
120 createdDate: instance.CreatedDate,
121});
122
123export const listGroups = Effect.fn(function* (identityStoreId: string) {
124 return yield* identitystore.listGroups
125 .items({
126 IdentityStoreId: identityStoreId,
127 MaxResults: 100,
128 })
129 .pipe(
130 Stream.runCollect,
131 Effect.map((groups) => Array.from(groups) as identitystore.Group[]),
132 );
133});