| 1 | import * as androidmanagement from "@distilled.cloud/gcp/androidmanagement_v1"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import { isResolved } from "../../Diff.ts"; |
| 4 | import * as Provider from "../../Provider.ts"; |
| 5 | import { Resource } from "../../Resource.ts"; |
| 6 | import { GcpEnvironment } from "../Environment.ts"; |
| 7 | import type { Providers } from "../Providers.ts"; |
| 8 | import { |
| 9 | DEFAULT_DURATION, |
| 10 | getEnrollmentToken, |
| 11 | jsonEqual, |
| 12 | lastSegment, |
| 13 | parentOf, |
| 14 | replaceOnIdentity, |
| 15 | toEnterpriseName, |
| 16 | toEnrollmentTokenName, |
| 17 | } from "./internal.ts"; |
| 18 | |
| 19 | export type EnterprisesEnrollmentTokenProps = { |
| 20 | /** |
| 21 | * Parent enterprise (`enterprises/{enterprise}` or `{enterprise}`). |
| 22 | * Immutable — changing it replaces the token. |
| 23 | */ |
| 24 | parent: string; |
| 25 | /** |
| 26 | * Policy initially applied to the enrolled device |
| 27 | * (`enterprises/{enterprise}/policies/{policy}` or `{policy}`). |
| 28 | * Immutable — changing it replaces the token. |
| 29 | */ |
| 30 | policyName?: string; |
| 31 | /** |
| 32 | * Whether personal usage is allowed on a device provisioned with this |
| 33 | * token. Immutable — changing it replaces the token. |
| 34 | */ |
| 35 | allowPersonalUsage?: |
| 36 | | androidmanagement.EnrollmentTokenAllowPersonalUsageEnum |
| 37 | | (string & {}); |
| 38 | /** |
| 39 | * Google authentication options during enrollment. Immutable — |
| 40 | * changing them replaces the token. |
| 41 | */ |
| 42 | googleAuthenticationOptions?: androidmanagement.GoogleAuthenticationOptions; |
| 43 | /** |
| 44 | * One-time-use token. Immutable — changing it replaces the token. |
| 45 | */ |
| 46 | oneTimeOnly?: boolean; |
| 47 | /** |
| 48 | * Token lifetime as a duration string (for example `3600s`). Defaults |
| 49 | * to ten years so the resource stays usable until deleted. |
| 50 | * Immutable — changing it replaces the token. |
| 51 | * @default "315360000s" |
| 52 | */ |
| 53 | duration?: string; |
| 54 | /** |
| 55 | * Arbitrary data exposed on the Device after enrollment (max 1024 |
| 56 | * characters). List/get return only a partial view and omit this |
| 57 | * field. Immutable — changing it replaces the token. |
| 58 | */ |
| 59 | additionalData?: string; |
| 60 | }; |
| 61 | |
| 62 | export type EnterprisesEnrollmentToken = Resource< |
| 63 | "GCP.AndroidManagement.EnterprisesEnrollmentToken", |
| 64 | EnterprisesEnrollmentTokenProps, |
| 65 | { |
| 66 | /** Resource name `enterprises/{enterprise}/enrollmentTokens/{token}`. */ |
| 67 | name: string; |
| 68 | /** Token id (last path segment). */ |
| 69 | enrollmentTokenId: string; |
| 70 | /** Parent enterprise name. */ |
| 71 | parent: string; |
| 72 | /** Project id used when the token was reconciled. */ |
| 73 | project: string; |
| 74 | /** Initial policy name. */ |
| 75 | policyName: string | undefined; |
| 76 | /** Personal-usage setting. */ |
| 77 | allowPersonalUsage: string | undefined; |
| 78 | /** Google authentication options. */ |
| 79 | googleAuthenticationOptions: |
| 80 | | androidmanagement.GoogleAuthenticationOptions |
| 81 | | undefined; |
| 82 | /** One-time-use flag. */ |
| 83 | oneTimeOnly: boolean | undefined; |
| 84 | /** Requested lifetime. */ |
| 85 | duration: string | undefined; |
| 86 | /** Additional data exposed on the enrolled device. */ |
| 87 | additionalData: string | undefined; |
| 88 | /** Token value passed to the device. */ |
| 89 | value: string | undefined; |
| 90 | /** QR code payload for enrollment. */ |
| 91 | qrCode: string | undefined; |
| 92 | /** RFC3339 expiration timestamp. */ |
| 93 | expirationTimestamp: string | undefined; |
| 94 | }, |
| 95 | never, |
| 96 | Providers |
| 97 | >; |
| 98 | |
| 99 | /** |
| 100 | * An Android Management enrollment token. |
| 101 | * |
| 102 | * Tokens are create-only (no patch API). Identity is the parent |
| 103 | * enterprise plus the server-assigned name. Changing parent, policy, |
| 104 | * duration, personal-usage, or additional data replaces the token. |
| 105 | * List/get return a partial view (`name`, `expirationTimestamp`, |
| 106 | * `allowPersonalUsage`, `value`, `qrCode`) and tokens have no labels, |
| 107 | * so Alchemy tracks a token only by the name recorded in state. |
| 108 | * |
| 109 | * ### Creating an Enrollment Token |
| 110 | * **Example:** Long-lived token |
| 111 | * ```typescript |
| 112 | * const token = yield* GCP.AndroidManagement.EnterprisesEnrollmentToken( |
| 113 | * "Enroll", |
| 114 | * { parent: enterprise.name }, |
| 115 | * ); |
| 116 | * ``` |
| 117 | * |
| 118 | * **Example:** One-time token for a policy |
| 119 | * ```typescript |
| 120 | * const token = yield* GCP.AndroidManagement.EnterprisesEnrollmentToken( |
| 121 | * "Enroll", |
| 122 | * { |
| 123 | * parent: enterprise.name, |
| 124 | * policyName: "default", |
| 125 | * oneTimeOnly: true, |
| 126 | * duration: "86400s", |
| 127 | * }, |
| 128 | * ); |
| 129 | * ``` |
| 130 | * |
| 131 | * @resource |
| 132 | * @category AndroidManagement |
| 133 | */ |
| 134 | export const EnterprisesEnrollmentToken = Resource<EnterprisesEnrollmentToken>( |
| 135 | "GCP.AndroidManagement.EnterprisesEnrollmentToken", |
| 136 | ); |
| 137 | |
| 138 | const toAttrs = ( |
| 139 | token: androidmanagement.EnrollmentToken, |
| 140 | project: string, |
| 141 | extras?: { |
| 142 | parent?: string; |
| 143 | policyName?: string; |
| 144 | googleAuthenticationOptions?: androidmanagement.GoogleAuthenticationOptions; |
| 145 | oneTimeOnly?: boolean; |
| 146 | duration?: string; |
| 147 | additionalData?: string; |
| 148 | }, |
| 149 | ) => { |
| 150 | const name = token.name ?? ""; |
| 151 | return { |
| 152 | name, |
| 153 | enrollmentTokenId: lastSegment(name), |
| 154 | parent: extras?.parent ?? parentOf(name), |
| 155 | project, |
| 156 | policyName: extras?.policyName ?? token.policyName, |
| 157 | allowPersonalUsage: token.allowPersonalUsage, |
| 158 | googleAuthenticationOptions: |
| 159 | extras?.googleAuthenticationOptions ?? token.googleAuthenticationOptions, |
| 160 | oneTimeOnly: extras?.oneTimeOnly ?? token.oneTimeOnly, |
| 161 | duration: extras?.duration ?? token.duration, |
| 162 | additionalData: extras?.additionalData ?? token.additionalData, |
| 163 | value: token.value, |
| 164 | qrCode: token.qrCode, |
| 165 | expirationTimestamp: token.expirationTimestamp, |
| 166 | }; |
| 167 | }; |
| 168 | |
| 169 | export const EnterprisesEnrollmentTokenProvider = () => |
| 170 | Provider.succeed(EnterprisesEnrollmentToken, { |
| 171 | stables: [ |
| 172 | "name", |
| 173 | "enrollmentTokenId", |
| 174 | "parent", |
| 175 | "project", |
| 176 | "expirationTimestamp", |
| 177 | ], |
| 178 | |
| 179 | diff: Effect.fn(function* ({ news, olds, output }) { |
| 180 | if (!isResolved(news)) return undefined; |
| 181 | const identity = replaceOnIdentity({ |
| 182 | previousParent: olds?.parent ?? output?.parent, |
| 183 | nextParent: news.parent, |
| 184 | extra: |
| 185 | (news.policyName !== undefined && |
| 186 | olds?.policyName !== undefined && |
| 187 | news.policyName !== olds.policyName) || |
| 188 | (news.duration !== undefined && |
| 189 | olds?.duration !== undefined && |
| 190 | news.duration !== olds.duration) || |
| 191 | (news.oneTimeOnly !== undefined && |
| 192 | olds?.oneTimeOnly !== undefined && |
| 193 | news.oneTimeOnly !== olds.oneTimeOnly) || |
| 194 | (news.allowPersonalUsage !== undefined && |
| 195 | output?.allowPersonalUsage !== undefined && |
| 196 | news.allowPersonalUsage !== output.allowPersonalUsage) || |
| 197 | (news.additionalData !== undefined && |
| 198 | olds?.additionalData !== undefined && |
| 199 | news.additionalData !== olds.additionalData) || |
| 200 | (news.googleAuthenticationOptions !== undefined && |
| 201 | olds?.googleAuthenticationOptions !== undefined && |
| 202 | !jsonEqual( |
| 203 | news.googleAuthenticationOptions, |
| 204 | olds.googleAuthenticationOptions, |
| 205 | )), |
| 206 | }); |
| 207 | return identity; |
| 208 | }), |
| 209 | |
| 210 | read: Effect.fn(function* ({ olds, output }) { |
| 211 | const env = yield* GcpEnvironment.current; |
| 212 | const parent = olds?.parent ?? output?.parent ?? ""; |
| 213 | const existing = yield* getEnrollmentToken(output?.name ?? ""); |
| 214 | if (existing === undefined) return undefined; |
| 215 | return toAttrs(existing, env.project, { |
| 216 | parent, |
| 217 | policyName: olds?.policyName ?? output?.policyName, |
| 218 | googleAuthenticationOptions: |
| 219 | olds?.googleAuthenticationOptions ?? |
| 220 | output?.googleAuthenticationOptions, |
| 221 | oneTimeOnly: olds?.oneTimeOnly ?? output?.oneTimeOnly, |
| 222 | duration: olds?.duration ?? output?.duration, |
| 223 | additionalData: olds?.additionalData ?? output?.additionalData, |
| 224 | }); |
| 225 | }), |
| 226 | |
| 227 | reconcile: Effect.fn(function* ({ news, output }) { |
| 228 | const env = yield* GcpEnvironment.current; |
| 229 | const parent = toEnterpriseName(news.parent); |
| 230 | const additionalData = news.additionalData; |
| 231 | const duration = news.duration ?? output?.duration ?? DEFAULT_DURATION; |
| 232 | const desired: androidmanagement.EnrollmentToken = { |
| 233 | policyName: news.policyName, |
| 234 | allowPersonalUsage: news.allowPersonalUsage, |
| 235 | googleAuthenticationOptions: news.googleAuthenticationOptions, |
| 236 | oneTimeOnly: news.oneTimeOnly, |
| 237 | duration, |
| 238 | additionalData, |
| 239 | }; |
| 240 | |
| 241 | let current = yield* getEnrollmentToken( |
| 242 | toEnrollmentTokenName(parent, output?.name), |
| 243 | ); |
| 244 | |
| 245 | if (current === undefined) { |
| 246 | current = yield* androidmanagement.createEnterprisesEnrollmentTokens({ |
| 247 | parent, |
| 248 | body: desired, |
| 249 | }); |
| 250 | } |
| 251 | |
| 252 | const name = current.name ?? output?.name ?? ""; |
| 253 | const fresh = (yield* getEnrollmentToken(name)) ?? current; |
| 254 | return toAttrs(fresh, env.project, { |
| 255 | parent, |
| 256 | policyName: news.policyName ?? output?.policyName, |
| 257 | googleAuthenticationOptions: |
| 258 | news.googleAuthenticationOptions ?? |
| 259 | output?.googleAuthenticationOptions, |
| 260 | oneTimeOnly: news.oneTimeOnly ?? output?.oneTimeOnly, |
| 261 | duration, |
| 262 | additionalData, |
| 263 | }); |
| 264 | }), |
| 265 | |
| 266 | delete: Effect.fn(function* ({ output }) { |
| 267 | if (output.name.length === 0) return; |
| 268 | yield* androidmanagement |
| 269 | .deleteEnterprisesEnrollmentTokens({ |
| 270 | name: output.name, |
| 271 | }) |
| 272 | .pipe(Effect.catchTag("NotFound", () => Effect.void)); |
| 273 | }), |
| 274 | }); |