-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/usdigitalresponse/cpf-reporter
- Loading branch information
Showing
49 changed files
with
49,185 additions
and
429 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,3 +144,5 @@ dist | |
# Terraform cache | ||
terraform/.terraform | ||
.tool-versions | ||
|
||
localstack/volume |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,126 @@ | ||
import { parseJWT, Decoded } from '@redwoodjs/api' | ||
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' | ||
|
||
/** | ||
* Represents the user attributes returned by the decoding the | ||
* Authentication provider's JWT together with an optional list of roles. | ||
*/ | ||
type RedwoodUser = Record<string, unknown> & { roles?: string[] } | ||
|
||
/** | ||
* Once you are ready to add authentication to your application | ||
* you'll build out requireAuth() with real functionality. For | ||
* now we just return `true` so that the calls in services | ||
* have something to check against, simulating a logged | ||
* in user that is allowed to access that service. | ||
* getCurrentUser returns the user information together with | ||
* an optional collection of roles used by requireAuth() to check | ||
* if the user is authenticated or has role-based access | ||
* | ||
* See https://redwoodjs.com/docs/authentication for more info. | ||
* !! BEWARE !! Anything returned from this function will be available to the | ||
* client--it becomes the content of `currentUser` on the web side (as well as | ||
* `context.currentUser` on the api side). You should carefully add additional | ||
* fields to the return object only once you've decided they are safe to be seen | ||
* if someone were to open the Web Inspector in their browser. | ||
* | ||
* @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples | ||
* | ||
* @param decoded - The decoded access token containing user info and JWT | ||
* claims like `sub`. Note, this could be null. | ||
* @param { token, SupportedAuthTypes type } - The access token itself as well | ||
* as the auth provider type | ||
* @param { APIGatewayEvent event, Context context } - An optional object which | ||
* contains information from the invoker such as headers and cookies, and the | ||
* context information about the invocation such as IP Address | ||
* @returns RedwoodUser | ||
*/ | ||
export const isAuthenticated = () => { | ||
return true | ||
export const getCurrentUser = async ( | ||
decoded: Decoded | ||
): Promise<RedwoodUser | null> => { | ||
if (!decoded) { | ||
return null | ||
} | ||
|
||
const { roles } = parseJWT({ decoded }) | ||
|
||
if (roles) { | ||
return { ...decoded, roles } | ||
} | ||
|
||
return { ...decoded } | ||
} | ||
|
||
export const hasRole = ({ roles }) => { | ||
return roles !== undefined | ||
/** | ||
* The user is authenticated if there is a currentUser in the context | ||
* | ||
* @returns {boolean} - If the currentUser is authenticated | ||
*/ | ||
export const isAuthenticated = (): boolean => { | ||
return !!context.currentUser | ||
} | ||
|
||
// This is used by the redwood directive | ||
// in ./api/src/directives/requireAuth | ||
/** | ||
* When checking role membership, roles can be a single value, a list, or none. | ||
* You can use Prisma enums too (if you're using them for roles), just import your enum type from `@prisma/client` | ||
*/ | ||
type AllowedRoles = string | string[] | undefined | ||
|
||
/** | ||
* Checks if the currentUser is authenticated (and assigned one of the given roles) | ||
* | ||
* @param roles: {@link AllowedRoles} - Checks if the currentUser is assigned one of these roles | ||
* | ||
* @returns {boolean} - Returns true if the currentUser is logged in and assigned one of the given roles, | ||
* or when no roles are provided to check against. Otherwise returns false. | ||
*/ | ||
export const hasRole = (roles: AllowedRoles): boolean => { | ||
if (!isAuthenticated()) { | ||
return false | ||
} | ||
|
||
const currentUserRoles = context.currentUser?.roles | ||
|
||
if (typeof roles === 'string') { | ||
if (typeof currentUserRoles === 'string') { | ||
// roles to check is a string, currentUser.roles is a string | ||
return currentUserRoles === roles | ||
} else if (Array.isArray(currentUserRoles)) { | ||
// roles to check is a string, currentUser.roles is an array | ||
return currentUserRoles?.some((allowedRole) => roles === allowedRole) | ||
} | ||
} | ||
|
||
if (Array.isArray(roles)) { | ||
if (Array.isArray(currentUserRoles)) { | ||
// roles to check is an array, currentUser.roles is an array | ||
return currentUserRoles?.some((allowedRole) => | ||
roles.includes(allowedRole) | ||
) | ||
} else if (typeof currentUserRoles === 'string') { | ||
// roles to check is an array, currentUser.roles is a string | ||
return roles.some((allowedRole) => currentUserRoles === allowedRole) | ||
} | ||
} | ||
|
||
// roles not found | ||
return false | ||
} | ||
|
||
/** | ||
* Use requireAuth in your services to check that a user is logged in, | ||
* whether or not they are assigned a role, and optionally raise an | ||
* error if they're not. | ||
* | ||
* @param roles?: {@link AllowedRoles} - When checking role membership, these roles grant access. | ||
* | ||
* @returns - If the currentUser is authenticated (and assigned one of the given roles) | ||
* | ||
* @throws {@link AuthenticationError} - If the currentUser is not authenticated | ||
* @throws {@link ForbiddenError} - If the currentUser is not allowed due to role permissions | ||
* | ||
* @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples | ||
*/ | ||
export const requireAuth = ({ roles }: { roles?: AllowedRoles } = {}) => { | ||
if (!isAuthenticated()) { | ||
throw new AuthenticationError("You don't have permission to do that.") | ||
} | ||
|
||
// Roles are passed in by the requireAuth directive if you have auth setup | ||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars | ||
export const requireAuth = ({ roles }) => { | ||
return isAuthenticated() | ||
if (roles && !hasRole(roles)) { | ||
throw new ForbiddenError("You don't have access to do that.") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { | ||
GetObjectCommand, | ||
PutObjectCommand, | ||
PutObjectCommandInput, | ||
S3Client, | ||
} from '@aws-sdk/client-s3' | ||
import {ReceiveMessageCommand, SendMessageCommand, SQSClient} from '@aws-sdk/client-sqs' | ||
import {getSignedUrl as awsGetSignedUrl} from '@aws-sdk/s3-request-presigner' | ||
|
||
function getS3Client() { | ||
let s3: S3Client; | ||
if (process.env.LOCALSTACK_HOSTNAME) { | ||
/* | ||
1. Make sure the local environment has awslocal installed. | ||
2. Use the commands to create a bucket to test with. | ||
- awslocal s3api create-bucket --bucket arpa-audit-reports --region us-west-2 --create-bucket-configuration '{"LocationConstraint": "us-west-2"}' | ||
3. Access bucket resource metadata through the following URL. | ||
- awslocal s3api list-buckets | ||
- awslocal s3api list-objects --bucket arpa-audit-reports | ||
*/ | ||
console.log('------------ USING LOCALSTACK ------------'); | ||
const endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT || 4566}`; | ||
console.log(`endpoint: ${endpoint}`); | ||
s3 = new S3Client({ | ||
endpoint, | ||
forcePathStyle: true, | ||
region: process.env.AWS_DEFAULT_REGION, | ||
}); | ||
} else { | ||
s3 = new S3Client(); | ||
} | ||
return s3; | ||
} | ||
|
||
async function sendPutObjectToS3Bucket(bucketName: string, key: string, body: any) { | ||
const s3 = getS3Client(); | ||
const uploadParams : PutObjectCommandInput = { | ||
Bucket: bucketName, | ||
Key: key, | ||
Body: body, | ||
ServerSideEncryption: 'AES256', | ||
}; | ||
await s3.send(new PutObjectCommand(uploadParams)); | ||
} | ||
|
||
async function sendHeadObjectToS3Bucket(bucketName: string, key: string) { | ||
const s3 = getS3Client(); | ||
const uploadParams : PutObjectCommandInput = { | ||
Bucket: bucketName, | ||
Key: key, | ||
}; | ||
await s3.send(new PutObjectCommand(uploadParams)); | ||
} | ||
|
||
/** | ||
* This function is a wrapper around the getSignedUrl function from the @aws-sdk/s3-request-presigner package. | ||
* Exists to organize the imports and to make it easier to mock in tests. | ||
*/ | ||
async function getSignedUrl(bucketName: string, key: string) { | ||
const s3 = getS3Client(); | ||
const baseParams = { Bucket: bucketName, Key: key }; | ||
return awsGetSignedUrl(s3, new GetObjectCommand(baseParams), { expiresIn: 60 }); | ||
} | ||
|
||
function getSQSClient() { | ||
let sqs: SQSClient; | ||
if (process.env.LOCALSTACK_HOSTNAME) { | ||
console.log('------------ USING LOCALSTACK FOR SQS ------------'); | ||
const endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT || 4566}`; | ||
sqs = new SQSClient({ endpoint, region: process.env.AWS_DEFAULT_REGION }); | ||
} else { | ||
sqs = new SQSClient(); | ||
} | ||
return sqs; | ||
} | ||
|
||
async function sendSqsMessage(queueUrl: string, messageBody: any) { | ||
const sqs = getSQSClient(); | ||
await sqs.send(new SendMessageCommand({ | ||
QueueUrl: queueUrl, | ||
MessageBody: JSON.stringify(messageBody), | ||
})); | ||
} | ||
|
||
async function receiveSqsMessage(queueUrl: string) { | ||
const sqs = getSQSClient(); | ||
// const receiveResp = await sqs.send(new ReceiveMessageCommand({ | ||
// QueueUrl: process.env.TASK_QUEUE_URL, WaitTimeSeconds: 20, MaxNumberOfMessages: 1, | ||
// })); | ||
|
||
// const receiveResp = await sqs.send(new ReceiveMessageCommand({ | ||
// QueueUrl: process.env.TASK_QUEUE_URL, WaitTimeSeconds: 20, MaxNumberOfMessages: 1, | ||
// })); | ||
|
||
await sqs.send(new ReceiveMessageCommand({ | ||
QueueUrl: queueUrl, WaitTimeSeconds: 20, MaxNumberOfMessages: 1, | ||
})); | ||
} | ||
|
||
export default { | ||
sendPutObjectToS3Bucket, | ||
sendHeadObjectToS3Bucket, | ||
getSignedUrl, | ||
sendSqsMessage, | ||
receiveSqsMessage, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
api/src/services/expenditureCategories/expenditureCategories.scenarios.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
api/src/services/reportingPeriods/reportingPeriods.scenarios.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.