1
- import { FastifyPluginAsync } from "fastify" ;
1
+ import fastify , { FastifyPluginAsync } from "fastify" ;
2
2
import { z } from "zod" ;
3
3
import { AppRoles } from "../../common/roles.js" ;
4
4
import {
@@ -22,12 +22,14 @@ import {
22
22
TransactWriteItem ,
23
23
GetItemCommand ,
24
24
TransactionCanceledException ,
25
+ InternalServerError ,
25
26
} from "@aws-sdk/client-dynamodb" ;
26
27
import { CloudFrontKeyValueStoreClient } from "@aws-sdk/client-cloudfront-keyvaluestore" ;
27
28
import {
28
29
genericConfig ,
29
30
EVENT_CACHED_DURATION ,
30
31
LinkryGroupUUIDToGroupNameMap ,
32
+ roleArns ,
31
33
} from "../../common/config.js" ;
32
34
import { marshall , unmarshall } from "@aws-sdk/util-dynamodb" ;
33
35
import rateLimiter from "api/plugins/rateLimiter.js" ;
@@ -44,13 +46,62 @@ import {
44
46
SigMemberRecord ,
45
47
} from "common/types/siglead.js" ;
46
48
import {
49
+ addMemberRecordToSig ,
47
50
fetchMemberRecords ,
48
51
fetchSigCounts ,
49
52
fetchSigDetail ,
50
53
} from "api/functions/siglead.js" ;
51
54
import { intersection } from "api/plugins/auth.js" ;
55
+ import {
56
+ FastifyZodOpenApiSchema ,
57
+ FastifyZodOpenApiTypeProvider ,
58
+ } from "fastify-zod-openapi" ;
59
+ import { withRoles , withTags } from "api/components/index.js" ;
60
+ import { AnyARecord } from "dns" ;
61
+ import { getEntraIdToken } from "api/functions/entraId.js" ;
62
+ import { getRoleCredentials } from "api/functions/sts.js" ;
63
+ import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager" ;
64
+ import { logger } from "api/sqs/logger.js" ;
65
+ import fastifyStatic from "@fastify/static" ;
66
+
67
+ const postAddSigMemberSchema = z . object ( {
68
+ sigGroupId : z . string ( ) . min ( 1 ) ,
69
+ email : z . string ( ) . min ( 1 ) , // TODO: verify email and @illinois .edu
70
+ designation : z . string ( ) . min ( 1 ) . max ( 1 ) ,
71
+ memberName : z . string ( ) . min ( 1 ) ,
72
+ } ) ;
52
73
53
74
const sigleadRoutes : FastifyPluginAsync = async ( fastify , _options ) => {
75
+ const getAuthorizedClients = async ( ) => {
76
+ if ( roleArns . Entra ) {
77
+ fastify . log . info (
78
+ `Attempting to assume Entra role ${ roleArns . Entra } to get the Entra token...` ,
79
+ ) ;
80
+ const credentials = await getRoleCredentials ( roleArns . Entra ) ;
81
+ const clients = {
82
+ smClient : new SecretsManagerClient ( {
83
+ region : genericConfig . AwsRegion ,
84
+ credentials,
85
+ } ) ,
86
+ dynamoClient : new DynamoDBClient ( {
87
+ region : genericConfig . AwsRegion ,
88
+ credentials,
89
+ } ) ,
90
+ } ;
91
+ fastify . log . info (
92
+ `Assumed Entra role ${ roleArns . Entra } to get the Entra token.` ,
93
+ ) ;
94
+ return clients ;
95
+ } else {
96
+ fastify . log . debug (
97
+ "Did not assume Entra role as no env variable was present" ,
98
+ ) ;
99
+ return {
100
+ smClient : fastify . secretsManagerClient ,
101
+ dynamoClient : fastify . dynamoClient ,
102
+ } ;
103
+ }
104
+ } ;
54
105
const limitedRoutes : FastifyPluginAsync = async ( fastify ) => {
55
106
/*fastify.register(rateLimiter, {
56
107
limit: 30,
@@ -163,6 +214,81 @@ const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
163
214
reply . code ( 200 ) . send ( sigMemCounts ) ;
164
215
} ,
165
216
) ;
217
+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . post (
218
+ "/addMember/:sigid" ,
219
+ {
220
+ schema : withRoles (
221
+ [ AppRoles . SIGLEAD_MANAGER ] ,
222
+ withTags ( [ "Sigid" ] , {
223
+ // response: {
224
+ // 201: z.object({
225
+ // id: z.string(),
226
+ // resource: z.string(),
227
+ // }),
228
+ // },
229
+ body : postAddSigMemberSchema ,
230
+ summary : "Add a member to a sig." ,
231
+ } ) ,
232
+ ) satisfies FastifyZodOpenApiSchema ,
233
+ onRequest : fastify . authorizeFromSchema ,
234
+ } ,
235
+ async ( request , reply ) => {
236
+ const { sigGroupId, email, designation, memberName } = request . body ;
237
+ const tableName = genericConfig . SigleadDynamoSigMemberTableName ;
238
+
239
+ // First try-catch: See if the member already exists
240
+ let sigMembers : SigMemberRecord [ ] ;
241
+ try {
242
+ sigMembers = await fetchMemberRecords (
243
+ sigGroupId ,
244
+ tableName ,
245
+ fastify . dynamoClient ,
246
+ ) ;
247
+ } catch ( error ) {
248
+ request . log . error (
249
+ `Could not verify the member does not already exist in the sig: ${ error instanceof Error ? error . toString ( ) : "Unknown error" } ` ,
250
+ ) ;
251
+ throw new DatabaseFetchError ( {
252
+ message : "Failed to fetch sig member records from Dynamo table." ,
253
+ } ) ;
254
+ }
255
+
256
+ for ( const sigMember of sigMembers ) {
257
+ if ( sigMember . email === email ) {
258
+ throw new ValidationError ( {
259
+ message : "Member already exists in sig." ,
260
+ } ) ;
261
+ }
262
+ }
263
+
264
+ const newMemberRecord : SigMemberRecord = request . body ;
265
+ // Second try-catch: Try to add the member to Dynamo and AAD, rolling back if failure
266
+ try {
267
+ //FIXME: this is failing due to auth
268
+ const entraIdToken = await getEntraIdToken (
269
+ await getAuthorizedClients ( ) ,
270
+ fastify . environmentConfig . AadValidClientId ,
271
+ ) ;
272
+
273
+ await addMemberRecordToSig (
274
+ newMemberRecord ,
275
+ tableName ,
276
+ fastify . dynamoClient ,
277
+ entraIdToken ,
278
+ ) ;
279
+ } catch ( error : any ) {
280
+ request . log . error (
281
+ `Error while adding member to sig: ${ error instanceof Error ? error . toString ( ) : "Unknown error" } ` ,
282
+ ) ;
283
+ throw error ;
284
+ }
285
+
286
+ // Send the response
287
+ reply . code ( 200 ) . send ( {
288
+ message : "Added member to sig." ,
289
+ } ) ;
290
+ } ,
291
+ ) ;
166
292
} ;
167
293
168
294
fastify . register ( limitedRoutes ) ;
0 commit comments