@cloud-copilot/iam-expand 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +269 -0
  3. package/dist/cjs/cli.d.ts +3 -0
  4. package/dist/cjs/cli.d.ts.map +1 -0
  5. package/dist/cjs/cli.js +76 -0
  6. package/dist/cjs/cli.js.map +1 -0
  7. package/dist/cjs/cli_utils.d.ts +27 -0
  8. package/dist/cjs/cli_utils.d.ts.map +1 -0
  9. package/dist/cjs/cli_utils.js +69 -0
  10. package/dist/cjs/cli_utils.js.map +1 -0
  11. package/dist/cjs/expand.d.ts +69 -0
  12. package/dist/cjs/expand.d.ts.map +1 -0
  13. package/dist/cjs/expand.js +118 -0
  14. package/dist/cjs/expand.js.map +1 -0
  15. package/dist/cjs/index.d.ts +2 -0
  16. package/dist/cjs/index.d.ts.map +1 -0
  17. package/dist/cjs/index.js +18 -0
  18. package/dist/cjs/index.js.map +1 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/cjs/stdin.d.ts +7 -0
  21. package/dist/cjs/stdin.d.ts.map +1 -0
  22. package/dist/cjs/stdin.js +34 -0
  23. package/dist/cjs/stdin.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +67 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/cli_utils.d.ts +27 -0
  29. package/dist/cli_utils.d.ts.map +1 -0
  30. package/dist/cli_utils.js +57 -0
  31. package/dist/cli_utils.js.map +1 -0
  32. package/dist/cli_utils.test.d.ts +2 -0
  33. package/dist/cli_utils.test.d.ts.map +1 -0
  34. package/dist/cli_utils.test.js +90 -0
  35. package/dist/cli_utils.test.js.map +1 -0
  36. package/dist/esm/cli.d.ts +3 -0
  37. package/dist/esm/cli.d.ts.map +1 -0
  38. package/dist/esm/cli.js +74 -0
  39. package/dist/esm/cli.js.map +1 -0
  40. package/dist/esm/cli_utils.d.ts +27 -0
  41. package/dist/esm/cli_utils.d.ts.map +1 -0
  42. package/dist/esm/cli_utils.js +63 -0
  43. package/dist/esm/cli_utils.js.map +1 -0
  44. package/dist/esm/expand.d.ts +69 -0
  45. package/dist/esm/expand.d.ts.map +1 -0
  46. package/dist/esm/expand.js +114 -0
  47. package/dist/esm/expand.js.map +1 -0
  48. package/dist/esm/index.d.ts +2 -0
  49. package/dist/esm/index.d.ts.map +1 -0
  50. package/dist/esm/index.js +2 -0
  51. package/dist/esm/index.js.map +1 -0
  52. package/dist/esm/package.json +3 -0
  53. package/dist/esm/stdin.d.ts +7 -0
  54. package/dist/esm/stdin.d.ts.map +1 -0
  55. package/dist/esm/stdin.js +31 -0
  56. package/dist/esm/stdin.js.map +1 -0
  57. package/dist/expand.d.ts +55 -0
  58. package/dist/expand.d.ts.map +1 -0
  59. package/dist/expand.js +94 -0
  60. package/dist/expand.js.map +1 -0
  61. package/dist/expand.test.d.ts +2 -0
  62. package/dist/expand.test.d.ts.map +1 -0
  63. package/dist/expand.test.js +382 -0
  64. package/dist/expand.test.js.map +1 -0
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +18 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/stdin.d.ts +7 -0
  70. package/dist/stdin.d.ts.map +1 -0
  71. package/dist/stdin.js +35 -0
  72. package/dist/stdin.js.map +1 -0
  73. package/package.json +33 -0
  74. package/postbuild.sh +13 -0
  75. package/src/cli.ts +82 -0
  76. package/src/cli_utils.test.ts +131 -0
  77. package/src/cli_utils.ts +78 -0
  78. package/src/expand.test.ts +523 -0
  79. package/src/expand.ts +185 -0
  80. package/src/index.ts +1 -0
  81. package/src/stdin.ts +34 -0
  82. package/tsconfig.cjs.json +11 -0
  83. package/tsconfig.esm.json +14 -0
  84. package/tsconfig.json +22 -0
package/src/expand.ts ADDED
@@ -0,0 +1,185 @@
1
+ import { iamActionExists, iamActionsForService, iamServiceExists, iamServiceKeys } from '@cloud-copilot/iam-data'
2
+
3
+ export enum InvalidActionBehavior {
4
+ Remove = "Remove",
5
+ Error = "Error",
6
+ Include = "Include",
7
+ }
8
+
9
+ /**
10
+ * Options for the expand function
11
+ *
12
+ */
13
+ export interface ExpandIamActionsOptions {
14
+ /**
15
+ * If true, a single `*` will be expanded to all actions for all services
16
+ * If false, a single `*` will be returned as is
17
+ * Default: false
18
+ */
19
+ expandAsterik: boolean
20
+
21
+ /**
22
+ * If true, `service:*` will be expanded to all actions for that service
23
+ * If false, `service:*` will be returned as is
24
+ * Default: false
25
+ */
26
+ expandServiceAsterik: boolean
27
+
28
+ /**
29
+ * If true, an error will be thrown if the action string is not in the correct format
30
+ * If false, an empty array will be returned
31
+ * Default: false
32
+ */
33
+ errorOnInvalidFormat: boolean
34
+
35
+ /**
36
+ * If true, an error will be thrown if the service in the action string does not exist
37
+ * If false, an empty array will be returned
38
+ * Default: false
39
+ */
40
+ errorOnMissingService: boolean
41
+
42
+ /**
43
+ * If true, only unique values will be returned, while maintaining order
44
+ * If false, all values will be returned, even if they are duplicates
45
+ * Default: false
46
+ */
47
+ distinct: boolean
48
+
49
+
50
+ /**
51
+ * The behavior to use when an invalid action is encountered without wildcards
52
+ * @{InvalidActionBehavior.Remove} will remove the invalid action from the output
53
+ * @{InvalidActionBehavior.Error} will throw an error if an invalid action is encountered
54
+ * @{InvalidActionBehavior.Include} will include the invalid action in the output
55
+ *
56
+ * Default: InvalidActionBehavior.Remove
57
+ */
58
+ invalidActionBehavior: InvalidActionBehavior
59
+
60
+ /**
61
+ * If true, the returned array will be sorted
62
+ * If false, the returned array will be in the order they were expanded
63
+ * Default: false
64
+ */
65
+ sort: boolean
66
+ }
67
+
68
+ const defaultOptions: ExpandIamActionsOptions = {
69
+ expandAsterik: false,
70
+ expandServiceAsterik: false,
71
+ errorOnInvalidFormat: false,
72
+ errorOnMissingService: false,
73
+ invalidActionBehavior: InvalidActionBehavior.Remove,
74
+ distinct: false,
75
+ sort: false
76
+ }
77
+
78
+ const allAsteriksPattern = /^\*+$/i
79
+
80
+ /**
81
+ * Expands an IAM action string that contains wildcards.
82
+ * If the action string contains no wildcards, it is returned as is.
83
+ * @see {@link ExpandIamActionsOptions} for options to customize behavior
84
+ *
85
+ * If any options are set to throw an error, the function will throw an error if validation fails for a single value.
86
+ *
87
+ * @param actionStringOrStrings An IAM action or array IAM action(s) that may contain wildcards
88
+ * @param overrideOptions Options to override the default behavior
89
+ * @returns An array of expanded action strings flattend to a single array
90
+ */
91
+ export function expandIamActions(actionStringOrStrings: string | string[], overrideOptions?: Partial<ExpandIamActionsOptions>): string[] {
92
+ const options = {...defaultOptions, ...overrideOptions}
93
+
94
+ if(!actionStringOrStrings) {
95
+ //Just in case the user passes in null or undefined
96
+ return []
97
+ }
98
+
99
+ if(Array.isArray(actionStringOrStrings)) {
100
+ let allMatches = actionStringOrStrings.flatMap(actionString => expandIamActions(actionString, options))
101
+ if(options.distinct) {
102
+ const aSet = new Set<string>()
103
+ allMatches = allMatches.filter((value) => {
104
+ if(aSet.has(value)) {
105
+ return false
106
+ }
107
+ aSet.add(value)
108
+ return true
109
+ })
110
+ }
111
+ if(options.sort) {
112
+ allMatches.sort()
113
+ }
114
+ return allMatches
115
+ }
116
+
117
+ const actionString = actionStringOrStrings.trim()
118
+
119
+ if(actionString.match(allAsteriksPattern)) {
120
+ if(options.expandAsterik) {
121
+ //If that's really what you want...
122
+ return iamServiceKeys().flatMap(
123
+ service => iamActionsForService(service).map(action => `${service}:${action}`)
124
+ )
125
+ }
126
+ return ['*']
127
+ }
128
+
129
+ if(!actionString.includes(':')) {
130
+ if(options.errorOnInvalidFormat) {
131
+ throw new Error(`Invalid action format: ${actionString}`)
132
+ }
133
+ return []
134
+ }
135
+
136
+ const parts = actionString.split(':')
137
+ if(parts.length !== 2) {
138
+ if(options.errorOnInvalidFormat) {
139
+ throw new Error(`Invalid action format: ${actionString}`)
140
+ }
141
+ return []
142
+ }
143
+
144
+ const [service, wildcardActions] = parts.map(part => part.toLowerCase())
145
+ if(!iamServiceExists(service)) {
146
+ if(options.errorOnMissingService) {
147
+ throw new Error(`Service not found: ${service}`)
148
+ }
149
+ return []
150
+ }
151
+
152
+ if(wildcardActions.match(allAsteriksPattern)) {
153
+ if(options.expandServiceAsterik) {
154
+ return iamActionsForService(service).map(action => `${service}:${action}`)
155
+ }
156
+ return [`${service}:*`]
157
+ }
158
+
159
+ if(!actionString.includes('*')) {
160
+ const actionExists = iamActionExists(service, wildcardActions)
161
+ if(actionExists) {
162
+ return [actionString]
163
+ }
164
+ if(options.invalidActionBehavior === InvalidActionBehavior.Remove) {
165
+ return []
166
+ } else if(options.invalidActionBehavior === InvalidActionBehavior.Include) {
167
+ return [actionString]
168
+ } else if(options.invalidActionBehavior === InvalidActionBehavior.Error) {
169
+ throw new Error(`Invalid action: ${actionString}`)
170
+ } else {
171
+ //This should never happen
172
+ throw new Error(`Invalid invalidActionBehavior: ${options.invalidActionBehavior}`)
173
+ }
174
+ }
175
+
176
+ const allActions = iamActionsForService(service)
177
+ const pattern = "^" + wildcardActions.replace(/\*/g, '.*?') + "$"
178
+ const regex = new RegExp(pattern, 'i')
179
+ const matchingActions = allActions.filter(action => regex.test(action)).map(action => `${service}:${action}`)
180
+ if(options.sort) {
181
+ matchingActions.sort()
182
+ }
183
+
184
+ return matchingActions
185
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './expand.js';
package/src/stdin.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { stdin } from 'process';
2
+ /**
3
+ * Read from stdin until the stream ends, timeout, or an error occurs
4
+ *
5
+ * @returns the string input from stdin
6
+ */
7
+ export async function readStdin(readWait: number | undefined): Promise<string> {
8
+ return new Promise((resolve, reject) => {
9
+ // If the input is not a TTY, we are most likely receiving data from a pipe.
10
+ const definitelyReceivingData = !process.stdin.isTTY
11
+ if(!readWait || readWait <= 0) {
12
+ readWait = definitelyReceivingData ? 10_000 : 20
13
+ }
14
+
15
+ let data = '';
16
+ setTimeout(() => {
17
+ if(data.length === 0) {
18
+ resolve(data)
19
+ }
20
+ }, readWait)
21
+
22
+ stdin.on('data', (chunk) => {
23
+ data += chunk;
24
+ });
25
+
26
+ stdin.on('end', () => {
27
+ resolve(data);
28
+ });
29
+
30
+ stdin.on('error', (err) => {
31
+ reject(err);
32
+ });
33
+ });
34
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+
4
+ "include": ["src/**/*"],
5
+ "exclude": ["**/*.test.ts"],
6
+
7
+ "compilerOptions": {
8
+ "rootDir": "src",
9
+ "outDir": "dist/cjs"
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+
4
+ "include": ["src/**/*"],
5
+ "exclude": ["**/*.test.ts"],
6
+
7
+ "compilerOptions": {
8
+ "target": "ES2020",
9
+ "module": "ES2020",
10
+ "moduleResolution": "node",
11
+ "rootDir": "src",
12
+ "outDir": "dist/esm"
13
+ }
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "es2022",
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "sourceMap": true,
8
+ "strict": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "lib": ["es2023", "DOM"],
12
+ "noUnusedLocals": false,
13
+ "noUnusedParameters": false,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": false,
16
+ "experimentalDecorators": true,
17
+ "emitDecoratorMetadata": true,
18
+ "esModuleInterop": false,
19
+ "forceConsistentCasingInFileNames": true,
20
+ },
21
+ "exclude": ["tests", "test", "dist", "bin", "**/bin", "**/dist", "node_modules", "cdk.out"]
22
+ }