1import * as iam from "@distilled.cloud/aws/iam";
2import type * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import type { Providers } from "../Providers.ts";
7import { toWireDays } from "../../Util/Duration.ts";
8
9export interface AccountPasswordPolicyProps extends Omit<
10 iam.UpdateAccountPasswordPolicyRequest,
11 "MaxPasswordAge"
12> {
13 /**
14 * Maximum password validity duration, e.g. `"90 days"` or
15 * `Duration.days(90)`. Sent to IAM as whole days (a bare number is
16 * milliseconds).
17 */
18 MaxPasswordAge?: Duration.Input;
19}
20
21export interface AccountPasswordPolicy extends Resource<
22 "AWS.IAM.AccountPasswordPolicy",
23 AccountPasswordPolicyProps,
24 iam.PasswordPolicy,
25 never,
26 Providers
27> {}
28
29/**
30 * The singleton IAM account password policy.
31 *
32 * `AccountPasswordPolicy` manages the account-wide password requirements that
33 * apply to IAM users with console passwords.
34 * ### Managing Password Rules
35 * **Example:** Require Strong Passwords
36 * ```typescript
37 * const policy = yield* AccountPasswordPolicy("PasswordPolicy", {
38 * MinimumPasswordLength: 16,
39 * RequireSymbols: true,
40 * RequireNumbers: true,
41 * RequireUppercaseCharacters: true,
42 * RequireLowercaseCharacters: true,
43 * AllowUsersToChangePassword: true,
44 * });
45 * ```
46 *
47 * @resource
48 */
49export const AccountPasswordPolicy = Resource<AccountPasswordPolicy>(
50 "AWS.IAM.AccountPasswordPolicy",
51);
52
53export const AccountPasswordPolicyProvider = () =>
54 Provider.succeed(AccountPasswordPolicy, {
55 read: Effect.fn(function* () {
56 const response = yield* iam
57 .getAccountPasswordPolicy({})
58 .pipe(
59 Effect.catchTag("NoSuchEntityException", () =>
60 Effect.succeed(undefined),
61 ),
62 );
63 return response?.PasswordPolicy;
64 }),
65 // Account-level singleton: IAM exposes no enumeration API, only
66 // `getAccountPasswordPolicy`. Return the single policy as a one-element
67 // array, or `[]` when none is set (typed `NoSuchEntityException`).
68 list: () =>
69 iam.getAccountPasswordPolicy({}).pipe(
70 Effect.map((response) => [response.PasswordPolicy]),
71 Effect.catchTag("NoSuchEntityException", () => Effect.succeed([])),
72 ),
73 reconcile: Effect.fn(function* ({ news, session }) {
74 // The account password policy is a singleton driven entirely by
75 // `updateAccountPasswordPolicy`, which is itself a full upsert.
76 // Observation is implicit in the API — there is nothing meaningful
77 // to diff because the request payload *is* the desired state.
78 const request = {
79 ...news,
80 MaxPasswordAge: toWireDays(news.MaxPasswordAge),
81 };
82 yield* iam.updateAccountPasswordPolicy(request);
83 yield* session.note("account-password-policy");
84 return request;
85 }),
86 delete: Effect.fn(function* () {
87 yield* iam
88 .deleteAccountPasswordPolicy({})
89 .pipe(Effect.catchTag("NoSuchEntityException", () => Effect.void));
90 }),
91 });