-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(babel): Fix opentelemetry api wrapping and allow it to be disabled (
#9298) **Problem** Some projects fail to build when opentelemetry is enabled. This is because the babel plugin which automatically wraps the api side functions failed to handle some syntax cases. In those cases the transpiled syntax would be invalid javascript. **Changes** 1. Supports default values in function arguments. 2. Supports some `AssignmentPattern` function arguments. 3. Add test fixtures. We can now see if the transpiled code is valid and as we expect. 4. Allow the api wrapping to be disabled via a `wrapApi` toml option 5. Expose the `wrapApi` toml option during opentelemetry setup. **Notes** There are still some syntax cases which aren't supported in the babel plugin. In this case we now bail out and do not modify the users source code rather than transpile into nonsense syntax. --------- Co-authored-by: Dominic Saadi <dominiceliassaadi@gmail.com>
- Loading branch information
1 parent
97b235f
commit 01f6565
Showing
22 changed files
with
1,301 additions
and
285 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
.../babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/code.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,16 @@ | ||
import gql from 'graphql-tag' | ||
|
||
import { createValidatorDirective } from '@redwoodjs/graphql-server' | ||
|
||
export const schema = gql` | ||
""" | ||
Use to skip authentication checks and allow public access. | ||
""" | ||
directive @skipAuth on FIELD_DEFINITION | ||
` | ||
|
||
const skipAuth = createValidatorDirective(schema, () => { | ||
return | ||
}) | ||
|
||
export default skipAuth |
13 changes: 13 additions & 0 deletions
13
...abel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/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,13 @@ | ||
import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' | ||
import gql from 'graphql-tag' | ||
import { createValidatorDirective } from '@redwoodjs/graphql-server' | ||
export const schema = gql` | ||
""" | ||
Use to skip authentication checks and allow public access. | ||
""" | ||
directive @skipAuth on FIELD_DEFINITION | ||
` | ||
const skipAuth = createValidatorDirective(schema, () => { | ||
return | ||
}) | ||
export default skipAuth |
102 changes: 102 additions & 0 deletions
102
packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/code.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,102 @@ | ||
import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' | ||
|
||
import { db } from 'src/lib/db' | ||
|
||
export const handler = async ( | ||
event, | ||
context | ||
) => { | ||
const forgotPasswordOptions = { | ||
handler: (user) => { | ||
return user | ||
}, | ||
|
||
expires: 60 * 60 * 24, | ||
|
||
errors: { | ||
usernameNotFound: 'Username not found', | ||
usernameRequired: 'Username is required', | ||
}, | ||
} | ||
|
||
const loginOptions = { | ||
handler: (user) => { | ||
return user | ||
}, | ||
|
||
errors: { | ||
usernameOrPasswordMissing: 'Both username and password are required', | ||
usernameNotFound: 'Username ${username} not found', | ||
incorrectPassword: 'Incorrect password for ${username}', | ||
}, | ||
|
||
expires: 60 * 60 * 24 * 365 * 10, | ||
} | ||
|
||
const resetPasswordOptions = { | ||
handler: (_user) => { | ||
return true | ||
}, | ||
|
||
allowReusedPassword: true, | ||
|
||
errors: { | ||
resetTokenExpired: 'resetToken is expired', | ||
resetTokenInvalid: 'resetToken is invalid', | ||
resetTokenRequired: 'resetToken is required', | ||
reusedPassword: 'Must choose a new password', | ||
}, | ||
} | ||
|
||
const signupOptions = { | ||
handler: ({ username, hashedPassword, salt, userAttributes }) => { | ||
return db.user.create({ | ||
data: { | ||
email: username, | ||
hashedPassword: hashedPassword, | ||
salt: salt, | ||
fullName: userAttributes['full-name'], | ||
}, | ||
}) | ||
}, | ||
|
||
passwordValidation: (_password) => { | ||
return true | ||
}, | ||
|
||
errors: { | ||
fieldMissing: '${field} is required', | ||
usernameTaken: 'Username `${username}` already in use', | ||
}, | ||
} | ||
|
||
const authHandler = new DbAuthHandler(event, context, { | ||
db: db, | ||
|
||
authModelAccessor: 'user', | ||
|
||
authFields: { | ||
id: 'id', | ||
username: 'email', | ||
hashedPassword: 'hashedPassword', | ||
salt: 'salt', | ||
resetToken: 'resetToken', | ||
resetTokenExpiresAt: 'resetTokenExpiresAt', | ||
}, | ||
|
||
cookie: { | ||
HttpOnly: true, | ||
Path: '/', | ||
SameSite: 'Strict', | ||
Secure: process.env.NODE_ENV !== 'development', | ||
|
||
}, | ||
|
||
forgotPassword: forgotPasswordOptions, | ||
login: loginOptions, | ||
resetPassword: resetPasswordOptions, | ||
signup: signupOptions, | ||
}) | ||
|
||
return await authHandler.invoke() | ||
} |
105 changes: 105 additions & 0 deletions
105
...ges/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/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,105 @@ | ||
import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' | ||
import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' | ||
import { db } from 'src/lib/db' | ||
export const handler = async (event, context) => { | ||
const __handler = async (event, context) => { | ||
const forgotPasswordOptions = { | ||
handler: (user) => { | ||
return user | ||
}, | ||
expires: 60 * 60 * 24, | ||
errors: { | ||
usernameNotFound: 'Username not found', | ||
usernameRequired: 'Username is required', | ||
}, | ||
} | ||
const loginOptions = { | ||
handler: (user) => { | ||
return user | ||
}, | ||
errors: { | ||
usernameOrPasswordMissing: 'Both username and password are required', | ||
usernameNotFound: 'Username ${username} not found', | ||
incorrectPassword: 'Incorrect password for ${username}', | ||
}, | ||
expires: 60 * 60 * 24 * 365 * 10, | ||
} | ||
const resetPasswordOptions = { | ||
handler: (_user) => { | ||
return true | ||
}, | ||
allowReusedPassword: true, | ||
errors: { | ||
resetTokenExpired: 'resetToken is expired', | ||
resetTokenInvalid: 'resetToken is invalid', | ||
resetTokenRequired: 'resetToken is required', | ||
reusedPassword: 'Must choose a new password', | ||
}, | ||
} | ||
const signupOptions = { | ||
handler: ({ username, hashedPassword, salt, userAttributes }) => { | ||
return db.user.create({ | ||
data: { | ||
email: username, | ||
hashedPassword: hashedPassword, | ||
salt: salt, | ||
fullName: userAttributes['full-name'], | ||
}, | ||
}) | ||
}, | ||
passwordValidation: (_password) => { | ||
return true | ||
}, | ||
errors: { | ||
fieldMissing: '${field} is required', | ||
usernameTaken: 'Username `${username}` already in use', | ||
}, | ||
} | ||
const authHandler = new DbAuthHandler(event, context, { | ||
db: db, | ||
authModelAccessor: 'user', | ||
authFields: { | ||
id: 'id', | ||
username: 'email', | ||
hashedPassword: 'hashedPassword', | ||
salt: 'salt', | ||
resetToken: 'resetToken', | ||
resetTokenExpiresAt: 'resetTokenExpiresAt', | ||
}, | ||
cookie: { | ||
HttpOnly: true, | ||
Path: '/', | ||
SameSite: 'Strict', | ||
Secure: process.env.NODE_ENV !== 'development', | ||
}, | ||
forgotPassword: forgotPasswordOptions, | ||
login: loginOptions, | ||
resetPassword: resetPasswordOptions, | ||
signup: signupOptions, | ||
}) | ||
return await authHandler.invoke() | ||
} | ||
const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') | ||
const RW_OTEL_WRAPPER_RESULT = await RW_OTEL_WRAPPER_TRACER.startActiveSpan( | ||
'redwoodjs:api:__MOCKED_API_FOLDER__:handler', | ||
async (span) => { | ||
span.setAttribute('code.function', 'handler') | ||
span.setAttribute('code.filepath', '__MOCKED_FILENAME__') | ||
try { | ||
const RW_OTEL_WRAPPER_INNER_RESULT = await __handler(event, context) | ||
span.end() | ||
return RW_OTEL_WRAPPER_INNER_RESULT | ||
} catch (error) { | ||
span.recordException(error) | ||
span.setStatus({ | ||
code: 2, | ||
message: | ||
error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], | ||
}) | ||
span.end() | ||
throw error | ||
} | ||
} | ||
) | ||
return RW_OTEL_WRAPPER_RESULT | ||
} |
23 changes: 23 additions & 0 deletions
23
...es/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/code.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,23 @@ | ||
import { authDecoder } from '@redwoodjs/auth-dbauth-api' | ||
import { createGraphQLHandler } from '@redwoodjs/graphql-server' | ||
|
||
import directives from 'src/directives/**/*.{js,ts}' | ||
import sdls from 'src/graphql/**/*.sdl.{js,ts}' | ||
import services from 'src/services/**/*.{js,ts}' | ||
|
||
import { getCurrentUser } from 'src/lib/auth' | ||
import { db } from 'src/lib/db' | ||
import { logger } from 'src/lib/logger' | ||
|
||
export const handler = createGraphQLHandler({ | ||
authDecoder, | ||
getCurrentUser, | ||
loggerConfig: { logger, options: {} }, | ||
directives, | ||
sdls, | ||
services, | ||
onException: () => { | ||
// Disconnect from your database with an unhandled exception. | ||
db.$disconnect() | ||
}, | ||
}) |
24 changes: 24 additions & 0 deletions
24
.../babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/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,24 @@ | ||
import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' | ||
import { authDecoder } from '@redwoodjs/auth-dbauth-api' | ||
import { createGraphQLHandler } from '@redwoodjs/graphql-server' | ||
import directives from 'src/directives/**/*.{js,ts}' | ||
import sdls from 'src/graphql/**/*.sdl.{js,ts}' | ||
import services from 'src/services/**/*.{js,ts}' | ||
import { getCurrentUser } from 'src/lib/auth' | ||
import { db } from 'src/lib/db' | ||
import { logger } from 'src/lib/logger' | ||
export const handler = createGraphQLHandler({ | ||
authDecoder, | ||
getCurrentUser, | ||
loggerConfig: { | ||
logger, | ||
options: {}, | ||
}, | ||
directives, | ||
sdls, | ||
services, | ||
onException: () => { | ||
// Disconnect from your database with an unhandled exception. | ||
db.$disconnect() | ||
}, | ||
}) |
61 changes: 61 additions & 0 deletions
61
packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/code.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,61 @@ | ||
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' | ||
|
||
import { db } from './db' | ||
|
||
export const getCurrentUser = async (session) => { | ||
if (!session || typeof session.id !== 'number') { | ||
throw new Error('Invalid session') | ||
} | ||
|
||
return await db.user.findUnique({ | ||
where: { id: session.id }, | ||
select: { id: true, roles: true, email: true }, | ||
}) | ||
} | ||
|
||
export const isAuthenticated = () => { | ||
return !!context.currentUser | ||
} | ||
|
||
export const hasRole = (roles) => { | ||
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 | ||
} | ||
|
||
export const requireAuth = ({ roles } = {}) => { | ||
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.") | ||
} | ||
} |
Oops, something went wrong.