Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 557ff54b2b435e5f1e789c6a8a4e1bebf2d7deb6 188 lines 5.4 kB view raw
1import { AuthenticationError } from 'apollo-server-express'; 2 3import { ErrorType, CoopError } from '../../utils/errors.js'; 4import { logErrorJson } from '../../utils/logging.js'; 5import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; 6 7/** Context shape required by rotateWebhookSigningKey (avoids importing resolvers). */ 8type RotateWebhookSigningKeyContext = { 9 getUser: () => { 10 orgId: string; 11 getPermissions: () => readonly string[]; 12 } | null | undefined; 13 dataSources: { 14 orgAPI: { rotateWebhookSigningKey: (orgId: string) => Promise<string> }; 15 }; 16}; 17 18const typeDefs = /* GraphQL */ ` 19 type ApiKey { 20 id: ID! 21 name: String! 22 description: String 23 isActive: Boolean! 24 createdAt: String! 25 lastUsedAt: String 26 createdBy: String 27 } 28 29 type RotateApiKeySuccessResponse { 30 apiKey: String! 31 record: ApiKey! 32 } 33 34 type RotateApiKeyError implements Error { 35 title: String! 36 status: Int! 37 type: [String!]! 38 pointer: String 39 detail: String 40 requestId: String 41 } 42 43 union RotateApiKeyResponse = RotateApiKeySuccessResponse | RotateApiKeyError 44 45 input RotateApiKeyInput { 46 name: String! 47 description: String 48 } 49 50 type RotateWebhookSigningKeySuccessResponse { 51 publicSigningKey: String! 52 } 53 54 type RotateWebhookSigningKeyError implements Error { 55 title: String! 56 status: Int! 57 type: [String!]! 58 pointer: String 59 detail: String 60 requestId: String 61 } 62 63 union RotateWebhookSigningKeyResponse = 64 RotateWebhookSigningKeySuccessResponse 65 | RotateWebhookSigningKeyError 66 67 type Query { 68 apiKey: String! 69 } 70 71 type Mutation { 72 rotateApiKey(input: RotateApiKeyInput!): RotateApiKeyResponse! 73 rotateWebhookSigningKey: RotateWebhookSigningKeyResponse! 74 } 75`; 76 77const Query: any = { 78 async apiKey(_: any, __: any, context: any) { 79 const user = context.getUser(); 80 if (!user || !user.orgId) { 81 throw new AuthenticationError('User must be authenticated'); 82 } 83 84 const apiKeyRecord = await context.services.ApiKeyService.getActiveApiKeyForOrg(user.orgId); 85 if (!apiKeyRecord) { 86 return process.env.NODE_ENV !== 'production' ? '' : ''; 87 } 88 // Return a message indicating the key exists but is hidden for security 89 return 'API key exists (hidden for security)'; 90 }, 91}; 92 93const Mutation: any = { 94 async rotateApiKey(_: any, { input }: any, context: any) { 95 const user = context.getUser(); 96 if (!user || !user.orgId) { 97 throw new AuthenticationError('User must be authenticated'); 98 } 99 if (!user.getPermissions().includes('MANAGE_ORG')) { 100 throw new AuthenticationError( 101 'User does not have permission to rotate the API key', 102 ); 103 } 104 105 try { 106 const { apiKey, record } = await context.services.ApiKeyService.rotateApiKey( 107 user.orgId, 108 input.name, 109 input.description || null, 110 user.id 111 ); 112 113 return gqlSuccessResult( 114 { 115 apiKey, 116 record: { 117 id: record.id, 118 name: record.name, 119 description: record.description, 120 isActive: record.isActive, 121 createdAt: record.createdAt.toISOString(), 122 lastUsedAt: record.lastUsedAt?.toISOString() || null, 123 createdBy: record.createdBy, 124 }, 125 }, 126 'RotateApiKeySuccessResponse' 127 ); 128 } catch (error) { 129 // Resolvers do not receive a request-scoped logger; use logErrorJson for structured server-side logging. 130 // eslint-disable-next-line no-restricted-syntax -- see comment above 131 logErrorJson({ message: 'Failed to rotate API key', error }); 132 return gqlErrorResult( 133 new CoopError({ 134 status: 500, 135 type: [ErrorType.InternalServerError], 136 title: 'Failed to rotate API key', 137 detail: 'An error occurred while rotating the API key', 138 name: 'InternalServerError', 139 shouldErrorSpan: true, 140 }), 141 ); 142 } 143 }, 144 async rotateWebhookSigningKey( 145 _: unknown, 146 __: Record<string, never>, 147 context: RotateWebhookSigningKeyContext, 148 ) { 149 const user = context.getUser(); 150 if (!user || !user.orgId) { 151 throw new AuthenticationError('User must be authenticated'); 152 } 153 if (!user.getPermissions().includes('MANAGE_ORG')) { 154 throw new AuthenticationError( 155 'User does not have permission to rotate the webhook signing key', 156 ); 157 } 158 159 try { 160 const publicSigningKey = 161 await context.dataSources.orgAPI.rotateWebhookSigningKey(user.orgId); 162 return gqlSuccessResult( 163 { publicSigningKey }, 164 'RotateWebhookSigningKeySuccessResponse', 165 ); 166 } catch (error) { 167 // Resolvers do not receive a request-scoped logger; use logErrorJson for structured server-side logging. 168 // eslint-disable-next-line no-restricted-syntax -- see comment above 169 logErrorJson({ 170 message: 'Failed to rotate webhook signing key', 171 error, 172 }); 173 return gqlErrorResult( 174 new CoopError({ 175 status: 500, 176 type: [ErrorType.InternalServerError], 177 title: 'Failed to rotate webhook signing key', 178 detail: 'An error occurred while rotating the webhook signing key', 179 name: 'InternalServerError', 180 shouldErrorSpan: true, 181 }), 182 ); 183 } 184 }, 185}; 186 187export const resolvers = { Query, Mutation }; 188export { typeDefs, Query, Mutation };