forked from redwoodjs/redwood
-
Notifications
You must be signed in to change notification settings - Fork 0
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 github.com:redwoodjs/redwood into feat/api-ski…
…p-prebuild * 'main' of github.com:redwoodjs/redwood: chore(deps): update dependency @types/uuid to v9.0.1 (redwoodjs#7680) chore(deps): update dependency @replayio/playwright to v0.3.23 (redwoodjs#7677) chore(deps): update dependency @npmcli/arborist to v6.2.3 (redwoodjs#7675) chore(deps): update dependency @envelop/types to v3.0.2 (redwoodjs#7674) chore: add codemod for clerk fix in v4.2.0 (redwoodjs#7676) chore(deps): update dependency @clerk/types to v3.28.1 (redwoodjs#7652) chore(deps): update dependency @envelop/testing to v5.0.6 (redwoodjs#7673) Update directives.md (redwoodjs#7670) fix(deps): update dependency vscode-languageserver-types to v3.17.3 (redwoodjs#7636) Fix `yarn rw exec` to set nonzero exit code on error (redwoodjs#7660)
- Loading branch information
Showing
14 changed files
with
425 additions
and
70 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
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
122 changes: 122 additions & 0 deletions
122
.../codemods/src/codemods/v4.2.x/updateClerkGetCurrentUser/__testfixtures__/default.input.js
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,122 @@ | ||
import { parseJWT } from '@redwoodjs/api' | ||
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' | ||
|
||
import { logger } from 'src/lib/logger' | ||
|
||
/** | ||
* 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 | ||
* | ||
* @param decoded - The decoded access token containing user info and JWT claims like `sub`. Note could be null. | ||
* @param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type | ||
* @param { APIGatewayEvent event, Context context } - An object which contains information from the invoker | ||
* such as headers and cookies, and the context information about the invocation such as IP Address | ||
* | ||
* @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples | ||
*/ | ||
export const getCurrentUser = async ( | ||
decoded, | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
{ token, type }, | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
{ event, context } | ||
) => { | ||
if (!decoded) { | ||
logger.warn('Missing decoded user') | ||
return null | ||
} | ||
|
||
const { roles } = parseJWT({ decoded }) | ||
|
||
if (roles) { | ||
return { ...decoded, roles } | ||
} | ||
|
||
return { ...decoded } | ||
} | ||
|
||
/** | ||
* The user is authenticated if there is a currentUser in the context | ||
* | ||
* @returns {boolean} - If the currentUser is authenticated | ||
*/ | ||
export const isAuthenticated = () => { | ||
return !!context.currentUser | ||
} | ||
|
||
/** | ||
* 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 | ||
|
||
/** | ||
* 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` | ||
*/ | ||
|
||
/** | ||
* 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.") | ||
} | ||
|
||
if (roles && !hasRole(roles)) { | ||
throw new ForbiddenError("You don't have access to do that.") | ||
} | ||
} |
129 changes: 129 additions & 0 deletions
129
...codemods/src/codemods/v4.2.x/updateClerkGetCurrentUser/__testfixtures__/default.output.js
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,129 @@ | ||
import { parseJWT } from '@redwoodjs/api' | ||
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' | ||
|
||
import { logger } from 'src/lib/logger' | ||
|
||
/** | ||
* 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 | ||
* | ||
* @param decoded - The decoded access token containing user info and JWT claims like `sub`. Note could be null. | ||
* @param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type | ||
* @param { APIGatewayEvent event, Context context } - An object which contains information from the invoker | ||
* such as headers and cookies, and the context information about the invocation such as IP Address | ||
* | ||
* @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples | ||
*/ | ||
export const getCurrentUser = async ( | ||
decoded, | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
{ token, type }, | ||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ | ||
{ event, context } | ||
) => { | ||
if (!decoded) { | ||
logger.warn('Missing decoded user') | ||
return null | ||
} | ||
|
||
const { roles } = parseJWT({ decoded }) | ||
|
||
const { privateMetadata, ...userWithoutPrivateMetadata } = decoded | ||
|
||
if (roles) { | ||
return { | ||
roles, | ||
...userWithoutPrivateMetadata, | ||
} | ||
} | ||
|
||
return { | ||
...userWithoutPrivateMetadata | ||
} | ||
} | ||
|
||
/** | ||
* The user is authenticated if there is a currentUser in the context | ||
* | ||
* @returns {boolean} - If the currentUser is authenticated | ||
*/ | ||
export const isAuthenticated = () => { | ||
return !!context.currentUser | ||
} | ||
|
||
/** | ||
* 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 | ||
|
||
/** | ||
* 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` | ||
*/ | ||
|
||
/** | ||
* 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.") | ||
} | ||
|
||
if (roles && !hasRole(roles)) { | ||
throw new ForbiddenError("You don't have access to do that.") | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...src/codemods/v4.2.x/updateClerkGetCurrentUser/__tests__/updateClerkGetCurrentUser.test.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
describe('clerk', () => { | ||
it('updates the getCurrentUser function', async () => { | ||
await matchTransformSnapshot('updateClerkGetCurrentUser', 'default') | ||
}) | ||
}) |
74 changes: 74 additions & 0 deletions
74
packages/codemods/src/codemods/v4.2.x/updateClerkGetCurrentUser/updateClerkGetCurrentUser.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import type { FileInfo, API, ObjectExpression } from 'jscodeshift' | ||
|
||
const newReturn = `userWithoutPrivateMetadata` | ||
const destructureStatement = `const { privateMetadata, ...${newReturn} } = decoded` | ||
|
||
export default function transform(file: FileInfo, api: API) { | ||
const j = api.jscodeshift | ||
const ast = j(file.source) | ||
|
||
// Insert `const { privateMetadata, ...userWithoutPrivateMetadata } = decoded` after `const { roles } = parseJWT({ decoded })` | ||
// | ||
// So, before... | ||
// | ||
// ```ts | ||
// const { roles } = parseJWT({ decoded }) | ||
// ``` | ||
// | ||
// and after... | ||
// | ||
// ```ts | ||
// const { roles } = parseJWT({ decoded }) | ||
// | ||
// const { privateMetadata, ...userWithoutPrivateMetadata } = decoded | ||
// ``` | ||
const parseJWTStatement = ast.find(j.VariableDeclaration, { | ||
declarations: [ | ||
{ | ||
type: 'VariableDeclarator', | ||
init: { | ||
type: 'CallExpression', | ||
callee: { | ||
name: 'parseJWT', | ||
}, | ||
}, | ||
}, | ||
], | ||
}) | ||
|
||
parseJWTStatement.insertAfter(destructureStatement) | ||
|
||
// Swap `decoded` with `userWithoutPrivateMetadata` in the two return statements | ||
ast | ||
.find(j.ReturnStatement, { | ||
argument: { | ||
type: 'ObjectExpression', | ||
properties: [ | ||
{ | ||
type: 'SpreadElement', | ||
argument: { | ||
name: 'decoded', | ||
}, | ||
}, | ||
], | ||
}, | ||
}) | ||
.replaceWith((path) => { | ||
const properties = ( | ||
path.value.argument as ObjectExpression | ||
).properties.filter( | ||
(property) => | ||
property.type !== 'SpreadElement' && property.name !== 'decoded' | ||
) | ||
|
||
properties.push(j.spreadElement(j.identifier(newReturn))) | ||
|
||
return j.returnStatement(j.objectExpression(properties)) | ||
}) | ||
|
||
return ast.toSource({ | ||
trailingComma: true, | ||
quote: 'single', | ||
lineTerminator: '\n', | ||
}) | ||
} |
Oops, something went wrong.