-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add directive to validate auth token and feature flag #122
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { GraphQLField } from 'graphql' | ||
import { defaultFieldResolver } from 'graphql' | ||
import { SchemaDirectiveVisitor } from 'graphql-tools' | ||
|
||
import { checkUserOrAdminTokenAccess } from './checkUserAccess' | ||
|
||
export class CheckAccessWithFeatureFlag extends SchemaDirectiveVisitor { | ||
public visitFieldDefinition(field: GraphQLField<any, any>) { | ||
const { resolve = defaultFieldResolver } = field | ||
|
||
field.resolve = async ( | ||
root: any, | ||
args: any, | ||
context: Context, | ||
info: any | ||
) => { | ||
const { | ||
clients: { masterdata }, | ||
} = context | ||
|
||
const config: { enable: boolean } = await masterdata.getDocument({ | ||
dataEntity: 'auth_validation_config', | ||
fields: ['enable'], | ||
id: 'storefront-permissions', | ||
}) | ||
|
||
if (config?.enable) { | ||
await checkUserOrAdminTokenAccess(context, field.astNode?.name?.value) | ||
} | ||
|
||
return resolve(root, args, context, info) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,61 @@ import type { GraphQLField } from 'graphql' | |
import { defaultFieldResolver } from 'graphql' | ||
import { SchemaDirectiveVisitor } from 'graphql-tools' | ||
|
||
export async function checkUserOrAdminTokenAccess( | ||
ctx: Context, | ||
operation?: string | ||
) { | ||
const { | ||
vtex: { adminUserAuthToken, storeUserAuthToken, logger }, | ||
clients: { identity, vtexId }, | ||
} = ctx | ||
|
||
if (!adminUserAuthToken && !storeUserAuthToken) { | ||
logger.warn({ | ||
message: `CheckUserAccess: No admin or store token was provided`, | ||
operation, | ||
}) | ||
throw new AuthenticationError('No admin or store token was provided') | ||
} | ||
|
||
if (adminUserAuthToken) { | ||
try { | ||
await identity.validateToken({ token: adminUserAuthToken }) | ||
} catch (err) { | ||
logger.warn({ | ||
error: err, | ||
message: `CheckUserAccess: Invalid admin token`, | ||
operation, | ||
}) | ||
throw new ForbiddenError('Unauthorized Access') | ||
} | ||
} else if (storeUserAuthToken) { | ||
let authUser = null | ||
|
||
try { | ||
authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken) | ||
if (!authUser?.user) { | ||
logger.warn({ | ||
message: `CheckUserAccess: No valid user found by store user token`, | ||
operation, | ||
}) | ||
authUser = null | ||
} | ||
} catch (err) { | ||
logger.warn({ | ||
error: err, | ||
message: `CheckUserAccess: Invalid store user token`, | ||
operation, | ||
}) | ||
authUser = null | ||
} | ||
|
||
if (!authUser) { | ||
throw new ForbiddenError('Unauthorized Access') | ||
} | ||
} | ||
} | ||
|
||
export class CheckUserAccess extends SchemaDirectiveVisitor { | ||
public visitFieldDefinition(field: GraphQLField<any, any>) { | ||
const { resolve = defaultFieldResolver } = field | ||
|
@@ -13,47 +68,7 @@ export class CheckUserAccess extends SchemaDirectiveVisitor { | |
context: Context, | ||
info: any | ||
) => { | ||
const { | ||
vtex: { adminUserAuthToken, storeUserAuthToken, logger }, | ||
clients: { identity, vtexId }, | ||
} = context | ||
|
||
if (!adminUserAuthToken && !storeUserAuthToken) { | ||
throw new AuthenticationError('No admin or store token was provided') | ||
} | ||
|
||
if (adminUserAuthToken) { | ||
try { | ||
await identity.validateToken({ token: adminUserAuthToken }) | ||
} catch (err) { | ||
logger.warn({ | ||
error: err, | ||
message: 'CheckUserAccess: Invalid admin token', | ||
token: adminUserAuthToken, | ||
}) | ||
throw new ForbiddenError('Unauthorized Access') | ||
} | ||
} else if (storeUserAuthToken) { | ||
let authUser = null | ||
|
||
try { | ||
authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken) | ||
if (!authUser?.user) { | ||
authUser = null | ||
} | ||
} catch (err) { | ||
logger.warn({ | ||
error: err, | ||
message: 'CheckUserAccess: Invalid store user token', | ||
token: adminUserAuthToken, | ||
}) | ||
authUser = null | ||
} | ||
|
||
if (!authUser) { | ||
throw new ForbiddenError('Unauthorized Access') | ||
} | ||
} | ||
await checkUserOrAdminTokenAccess(context, field.astNode?.name?.value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm missing something, here, but from what I understood we now have both directives:
Why have both? The I think I misunderstood something though, can you explain the idea of the two directives with more details? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you said about the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, got it! I was confusing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Final question then: will we be able to remove this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is the intention. I almost removed it in this PR, but I let it go for the next one. |
||
|
||
return resolve(root, args, context, info) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this cause us to send duplicated checks for validating these tokens? Or are we not already doing this validation on any of these fields today?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was implemented previously. I refactored it into a new function to reuse in another directive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw that, but I mean if there are other files that are doing these checks inside field resolvers. Some of the fields where you added this new
checkAccessFeatureFlag
directive may already be doing these checks internally, so we'd do it twice now, duplicating the work. If the only place doing this was the function you refactored than we should be good.