Skip to content

Commit 2277194

Browse files
authored
Fix auto revoke feature bug (#8)
1 parent 88ce9f9 commit 2277194

File tree

6 files changed

+259
-67
lines changed

6 files changed

+259
-67
lines changed

apps/web-ui/custom-server/dynamodbApp.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,10 @@ async function main() {
2525
logLevel: logLevel,
2626
});
2727

28-
const schedulerProvider = createSchedulerProvider({
29-
tableNamePrefix: process.env.DYNAMO_TABLE_PREFIX!,
30-
region: "us-west-2",
31-
logLevel: logLevel,
32-
targetSNSTopicArn: process.env.SCHEDULER_TARGET_SNS_ARN!,
33-
roleArn: process.env.SCHEDULER_GROUP_ROLE_ARN!,
34-
schedulerGroupName: process.env.SCHEDULER_GROUP_NAME!,
35-
});
36-
3728
const config = await createConfigProvider({
3829
catalogs: [unicornRentalCatalog],
3930
});
40-
createStampHubHTTPServer({ db: dynamodbDB, config: config, identity: dynamodBIdentity, scheduler: schedulerProvider }, 4000);
31+
createStampHubHTTPServer({ db: dynamodbDB, config: config, identity: dynamodBIdentity }, 4000);
4132

4233
const pluginRouter = createPluginRouter({ basePath: "/plugin", plugins: {} });
4334

apps/web-ui/src/components/approval-flow/requestForm.tsx

Lines changed: 91 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
"use client";
22
import { approvalRequestSubmit } from "@/server-actions/approval-flow/approvalRequestSubmit";
3-
import { Flex, Text, Button, Grid, Box, Card, Container, Link, Heading, Separator, Table, TextField, TextArea, Select, Badge } from "@radix-ui/themes";
3+
import {
4+
Flex,
5+
Text,
6+
Button,
7+
Grid,
8+
Box,
9+
Card,
10+
Container,
11+
Link,
12+
Heading,
13+
Separator,
14+
Table,
15+
TextField,
16+
TextArea,
17+
Select,
18+
Badge,
19+
Checkbox,
20+
} from "@radix-ui/themes";
421
import { useFormState, useFormStatus } from "react-dom";
522
import { ApprovalFlow } from "@/type";
623
import { InputParamsFormInput } from "./inputParam";
@@ -21,8 +38,8 @@ const parseDuration = (duration: string): { days: number; hours: number } | null
2138
};
2239

2340
// Helper to format days and hours into ISO 8601 duration
24-
const formatDuration = (days: number, hours: number): string => {
25-
if (days === 0 && hours === 0) return "";
41+
const formatDuration = (days: number, hours: number): string | undefined => {
42+
if (days === 0 && hours === 0) return undefined;
2643
if (days === 0) return `PT${hours}H`;
2744
if (hours === 0) return `P${days}D`;
2845
return `P${days}DT${hours}H`;
@@ -31,7 +48,7 @@ const formatDuration = (days: number, hours: number): string => {
3148
function AutoRevokeDurationInput({
3249
autoRevoke,
3350
}: {
34-
autoRevoke?: {
51+
autoRevoke: {
3552
enabled: boolean;
3653
required: boolean;
3754
maxDuration?: string;
@@ -40,38 +57,36 @@ function AutoRevokeDurationInput({
4057
const [days, setDays] = useState<number>(0);
4158
const [hours, setHours] = useState<number>(0);
4259
const [error, setError] = useState<string | null>(null);
60+
const [enableAutoRevoke, setEnableAutoRevoke] = useState<boolean>(autoRevoke.required);
4361

4462
// Use useMemo to prevent recalculation on every render
45-
const defaultMaxDuration = useMemo(() => {
46-
return autoRevoke?.maxDuration ? parseDuration(autoRevoke.maxDuration) : { days: 30, hours: 23 };
63+
const maxDuration = useMemo(() => {
64+
return autoRevoke?.maxDuration ? parseDuration(autoRevoke.maxDuration) : { days: 30, hours: 24 };
4765
}, [autoRevoke?.maxDuration]);
4866

4967
// Validate and update the hidden input value when days or hours change
5068
useEffect(() => {
5169
const validateDuration = () => {
70+
if (!enableAutoRevoke) {
71+
setError(null);
72+
return true;
73+
}
74+
5275
if (days === 0 && hours === 0) {
5376
if (autoRevoke?.required) {
5477
setError("Duration is required");
5578
return false;
5679
}
5780
}
5881

59-
if (days > 30) {
60-
setError("Days cannot exceed 30");
61-
return false;
62-
}
63-
64-
if (hours > 24) {
65-
setError("Hours cannot exceed 24");
66-
return false;
67-
}
68-
69-
if (defaultMaxDuration) {
70-
const totalHours = days * 24 + hours;
71-
const maxTotalHours = defaultMaxDuration.days * 24 + defaultMaxDuration.hours;
82+
if (maxDuration) {
83+
if (days > maxDuration.days) {
84+
setError(`Days cannot exceed ${maxDuration.days}`);
85+
return false;
86+
}
7287

73-
if (totalHours > maxTotalHours) {
74-
setError(`Duration cannot exceed ${defaultMaxDuration.days} days and ${defaultMaxDuration.hours} hours`);
88+
if (hours > maxDuration.hours) {
89+
setError(`Hours cannot exceed ${maxDuration.hours}`);
7590
return false;
7691
}
7792
}
@@ -81,51 +96,72 @@ function AutoRevokeDurationInput({
8196
};
8297

8398
validateDuration();
84-
}, [days, hours, autoRevoke, defaultMaxDuration]);
99+
}, [days, hours, autoRevoke, maxDuration, enableAutoRevoke]);
85100

86101
if (!autoRevoke?.enabled) return null;
87102

103+
// Only set the duration value if auto-revoke is enabled
104+
const durationValue = enableAutoRevoke ? formatDuration(days, hours) : undefined;
105+
88106
return (
89107
<Flex direction="column" gap="2">
90108
<Flex align="center" gap="2">
91-
<Heading size="3">Duration Until Auto Revoke</Heading>
109+
<Heading size="3">Auto Revoke</Heading>
92110
<Badge color="amber" size="1">
93111
Preview
94112
</Badge>
95113
</Flex>
96-
<Text size="2" color="gray">
97-
Specify how long this access should be granted (maximum: {defaultMaxDuration?.days} days and {defaultMaxDuration?.hours} hours)
98-
</Text>
99-
<Flex gap="2" align="center">
100-
<TextField.Root
101-
type="number"
102-
min="0"
103-
max="30"
104-
value={days.toString()}
105-
onChange={(e) => setDays(parseInt(e.target.value) || 0)}
106-
placeholder="Days"
107-
aria-label="Days"
108-
style={{ width: "30px" }}
109-
/>
110-
<Text>days</Text>
111-
<TextField.Root
112-
type="number"
113-
min="0"
114-
max="23"
115-
value={hours.toString()}
116-
onChange={(e) => setHours(parseInt(e.target.value) || 0)}
117-
placeholder="Hours"
118-
aria-label="Hours"
119-
style={{ width: "30px" }}
120-
/>
121-
<Text>hours</Text>
122-
</Flex>
123-
{error && (
124-
<Text size="2" color="red">
125-
{error}
126-
</Text>
114+
115+
{!autoRevoke.required && (
116+
<Flex gap="2" align="center">
117+
<Checkbox id="enable-auto-revoke" checked={enableAutoRevoke} onCheckedChange={(checked) => setEnableAutoRevoke(checked === true)} />
118+
<Text as="label" size="2" htmlFor="enable-auto-revoke">
119+
Enable automatic revocation after specified duration
120+
</Text>
121+
</Flex>
122+
)}
123+
124+
{(autoRevoke.required || enableAutoRevoke) && (
125+
<>
126+
<Text size="2" color="gray">
127+
Specify how long this access should be granted (maximum: {maxDuration?.days} days and {maxDuration?.hours} hours)
128+
</Text>
129+
<Flex gap="2" align="center">
130+
<TextField.Root
131+
type="number"
132+
min="0"
133+
max={maxDuration?.days.toString()}
134+
value={days.toString()}
135+
onChange={(e) => setDays(parseInt(e.target.value) || 0)}
136+
placeholder="Days"
137+
aria-label="Days"
138+
style={{ width: "80px" }}
139+
/>
140+
<Text>days</Text>
141+
<TextField.Root
142+
type="number"
143+
min="0"
144+
max={maxDuration?.hours.toString()}
145+
value={hours.toString()}
146+
onChange={(e) => setHours(parseInt(e.target.value) || 0)}
147+
placeholder="Hours"
148+
aria-label="Hours"
149+
style={{ width: "80px" }}
150+
/>
151+
<Text>hours</Text>
152+
</Flex>
153+
{error && (
154+
<Text size="2" color="red">
155+
{error}
156+
</Text>
157+
)}
158+
</>
159+
)}
160+
161+
{/* Only include the hidden input when auto-revoke is enabled */}
162+
{durationValue !== undefined && enableAutoRevoke && (
163+
<input type="hidden" name="autoRevokeDuration" value={durationValue} required={autoRevoke?.required} />
127164
)}
128-
<input type="hidden" name="autoRevokeDuration" value={formatDuration(days, hours)} required={autoRevoke?.required} />
129165
</Flex>
130166
);
131167
}

catalogs/example/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ const unicornRentalApplicationConfig: ApprovalFlowConfig = {
193193
approver: { approverType: "approvalFlow" },
194194
handlers: unicornRentalApplicationHandler,
195195
enableRevoke: true,
196+
autoRevoke: {
197+
enabled: true,
198+
defaultSettings: {
199+
required: false,
200+
},
201+
},
196202
};
197203

198204
const unicornRentalApplicationByStableApproverConfig: ApprovalFlowConfig = {

docs/operation/deployment-with-iam-idc-catalog.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ async function main() {
225225
});
226226

227227
const schedulerProvider = createSchedulerProvider({
228-
tableNamePrefix: tableNamePrefix,
229228
targetSNSTopicArn: process.env.SCHEDULER_TARGET_SNS_ARN!,
230229
roleArn: process.env.SCHEDULER_GROUP_ROLE_ARN!,
231230
schedulerGroupName: process.env.SCHEDULER_GROUP_NAME!,

0 commit comments

Comments
 (0)