From d95eae6f117db3e931050d133ec5da422d12b201 Mon Sep 17 00:00:00 2001 From: Enzo Mercanti Date: Fri, 17 May 2024 15:23:53 -0300 Subject: [PATCH 1/4] chore: improve metrics/logs for checkUserAccess/checkAdminAccess --- node/clients/IdentityClient.ts | 10 + node/directives/checkAdminAccess.ts | 136 ++++++++----- node/directives/checkUserAccess.ts | 291 ++++++++++++++-------------- node/metrics/auth.ts | 3 + 4 files changed, 254 insertions(+), 186 deletions(-) diff --git a/node/clients/IdentityClient.ts b/node/clients/IdentityClient.ts index b80d658..5fa811b 100644 --- a/node/clients/IdentityClient.ts +++ b/node/clients/IdentityClient.ts @@ -14,4 +14,14 @@ export default class IdentityClient extends JanusClient { public async validateToken({ token }: { token: string }): Promise { return this.http.post('/api/vtexid/credential/validate', { token }) } + + public async getToken({ + appkey, + apptoken, + }: { + appkey: string + apptoken: string + }): Promise { + return this.http.post('/api/vtexid/apptoken/login', { appkey, apptoken }) + } } diff --git a/node/directives/checkAdminAccess.ts b/node/directives/checkAdminAccess.ts index 967122c..73ba34b 100644 --- a/node/directives/checkAdminAccess.ts +++ b/node/directives/checkAdminAccess.ts @@ -16,68 +16,116 @@ export class CheckAdminAccess extends SchemaDirectiveVisitor { info: any ) => { const { - vtex: { adminUserAuthToken, logger }, + vtex: { adminUserAuthToken, storeUserAuthToken, logger }, clients: { identity }, } = context + // check if has admin token and if it is valid + const hasAdminToken = !!adminUserAuthToken + let hasValidAdminToken + // this is used to check if the token is valid by current standards + let hasCurrentValidAdminToken = false + + if (hasAdminToken) { + try { + const authUser = await identity.validateToken({ + token: adminUserAuthToken as string, + }) + + // we set this flag to true if the token is valid by current standards + // in the future we should remove this line + hasCurrentValidAdminToken = true + + if (authUser?.audience === 'admin') { + hasValidAdminToken = true + } else { + hasValidAdminToken = false + } + } catch (err) { + // noop so we leave hasValidAdminToken as undefined + } + } + + // check if has api token and if it is valid + const apiToken = context?.headers['vtex-api-apptoken'] as string + const appKey = context?.headers['vtex-api-appkey'] as string + const hasApiToken = !!(apiToken?.length && appKey?.length) + let hasValidApiToken + + if (hasApiToken) { + try { + const { token } = await identity.getToken({ + appkey: appKey, + apptoken: apiToken, + }) + + const authUser = await identity.validateToken({ + token, + }) + + if (authUser?.audience === 'admin') { + hasValidApiToken = true + } else { + hasValidApiToken = false + } + } catch (err) { + // noop so we leave hasValidApiToken as undefined + } + } + + // check if has store token but don't check if it is valid as we don't need it + const hasStoreToken = !!storeUserAuthToken + + // now we emit a metric with all the collected data before we proceed const operation = field.astNode?.name?.value ?? context.request.url - const metric = new AuthMetric( - context.vtex.account, + const auditMetric = new AuthMetric( + context?.vtex?.account, { operation, - forwardedHost: context.request.header['x-forwarded-host'] as string, - caller: context.request.header['x-vtex-caller'] as string, - userAgent: context.request.header['user-agent'] as string, - hasAdminToken: !!adminUserAuthToken, - hasStoreToken: false, - hasApiToken: false, + forwardedHost: context?.request?.headers[ + 'x-forwarded-host' + ] as string, + caller: context?.request?.headers['x-vtex-caller'] as string, + userAgent: context?.request?.headers['user-agent'] as string, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, }, - 'CheckAdminAccess' + 'CheckAdminAccessAudit' ) - if (!adminUserAuthToken) { - metric.error = 'No admin token provided' - sendAuthMetric(logger, metric) + sendAuthMetric(logger, auditMetric) + + if (!hasAdminToken) { logger.warn({ - message: 'CheckAdminAccess: No admin token provided', - userAgent: context.request.header['user-agent'], - vtexCaller: context.request.header['x-vtex-caller'], - forwardedHost: context.request.header['x-forwarded-host'], + message: 'CheckAdminAccess: No token provided', + userAgent: context?.request?.headers['user-agent'], + vtexCaller: context?.request?.headers['x-vtex-caller'], + forwardedHost: context?.request?.headers['x-forwarded-host'], operation, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, }) throw new AuthenticationError('No token was provided') } - try { - const authUser = await identity.validateToken({ - token: adminUserAuthToken, - }) - - // This is the first step before actually enabling this code. - // For now we only log in case of errors, but in follow up commits - // we should also throw an exception inside this if in case of errors - if (!authUser?.audience || authUser?.audience !== 'admin') { - metric.error = 'Token is not an admin token' - sendAuthMetric(logger, metric) - logger.warn({ - message: `CheckUserAccess: Token is not an admin token`, - userAgent: context.request.header['user-agent'], - vtexCaller: context.request.header['x-vtex-caller'], - forwardedHost: context.request.header['x-forwarded-host'], - operation, - }) - } - } catch (err) { - metric.error = 'Invalid token' - sendAuthMetric(logger, metric) + if (!hasCurrentValidAdminToken) { logger.warn({ - error: err, message: 'CheckAdminAccess: Invalid token', - userAgent: context.request.header['user-agent'], - vtexCaller: context.request.header['x-vtex-caller'], - forwardedHost: context.request.header['x-forwarded-host'], + userAgent: context?.request?.headers['user-agent'], + vtexCaller: context?.request?.headers['x-vtex-caller'], + forwardedHost: context?.request?.headers['x-forwarded-host'], operation, - token: adminUserAuthToken, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, }) throw new ForbiddenError('Unauthorized Access') } diff --git a/node/directives/checkUserAccess.ts b/node/directives/checkUserAccess.ts index eb3af26..602b077 100644 --- a/node/directives/checkUserAccess.ts +++ b/node/directives/checkUserAccess.ts @@ -6,147 +6,6 @@ import { SchemaDirectiveVisitor } from 'graphql-tools' import { getActiveUserByEmail } from '../resolvers/Queries/Users' import sendAuthMetric, { AuthMetric } from '../metrics/auth' -export async function checkUserOrAdminTokenAccess( - ctx: Context, - operation?: string -) { - const { - vtex: { adminUserAuthToken, storeUserAuthToken, logger }, - clients: { identity, vtexId }, - } = ctx - - const metric = new AuthMetric( - ctx.vtex.account, - { - operation: operation ?? ctx.request.url, - forwardedHost: ctx.request.header['x-forwarded-host'] as string, - caller: ctx.request.header['x-vtex-caller'] as string, - userAgent: ctx.request.header['user-agent'] as string, - hasAdminToken: !!adminUserAuthToken, - hasStoreToken: !!storeUserAuthToken, - hasApiToken: false, - }, - 'CheckUserAccess' - ) - - if (!adminUserAuthToken && !storeUserAuthToken) { - metric.error = 'No admin or store token was provided' - sendAuthMetric(logger, metric) - logger.warn({ - message: `CheckUserAccess: No admin or store token was provided`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - throw new AuthenticationError('No admin or store token was provided') - } - - if (adminUserAuthToken) { - try { - const authUser = await identity.validateToken({ - token: adminUserAuthToken, - }) - - // This is the first step before actually enabling this code. - // For now we only log in case of errors, but in follow up commits - // we should also throw an exception inside this if in case of errors - if (!authUser?.audience || authUser?.audience !== 'admin') { - metric.error = 'Token is not an admin token' - sendAuthMetric(logger, metric) - logger.warn({ - message: `CheckUserAccess: Token is not an admin token`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - } - } catch (err) { - metric.error = 'Invalid admin token' - sendAuthMetric(logger, metric) - logger.warn({ - error: err, - message: `CheckUserAccess: Invalid admin token`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - throw new ForbiddenError('Unauthorized Access') - } - } else if (storeUserAuthToken) { - let authUser = null - - try { - authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken) - if (!authUser?.user) { - metric.error = 'No valid user found by store user token' - sendAuthMetric(logger, metric) - logger.warn({ - message: `CheckUserAccess: No valid user found by store user token`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - authUser = null - } else { - // This is the first step before actually enabling this code. - // For now we only log in case of errors, but in follow up commits - // we will remove this additional try/catch and set authUser = null - // in case of errors - try { - const user = (await getActiveUserByEmail( - null, - { email: authUser?.user }, - ctx - )) as { roleId: string } | null - - if (!user?.roleId) { - metric.error = 'No active user found by store user token' - sendAuthMetric(logger, metric) - logger.warn({ - message: `CheckUserAccess: No active user found by store user token`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - } - } catch (err) { - metric.error = 'Error getting user by email' - sendAuthMetric(logger, metric) - logger.warn({ - error: err, - message: `CheckUserAccess: Error getting user by email`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - } - } - } catch (err) { - metric.error = 'Invalid store user token' - sendAuthMetric(logger, metric) - logger.warn({ - error: err, - message: `CheckUserAccess: Invalid store user token`, - userAgent: ctx.request.header['user-agent'], - vtexCaller: ctx.request.header['x-vtex-caller'], - forwardedHost: ctx.request.header['x-forwarded-host'], - operation, - }) - authUser = null - } - - if (!authUser) { - throw new ForbiddenError('Unauthorized Access') - } - } -} - export class CheckUserAccess extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field @@ -157,7 +16,155 @@ export class CheckUserAccess extends SchemaDirectiveVisitor { context: Context, info: any ) => { - await checkUserOrAdminTokenAccess(context, field.astNode?.name?.value) + const { + vtex: { adminUserAuthToken, storeUserAuthToken, logger }, + clients: { identity, vtexId }, + } = context + + // check if has admin token and if it is valid + const hasAdminToken = !!adminUserAuthToken + let hasValidAdminToken + // this is used to check if the token is valid by current standards + let hasCurrentValidAdminToken = false + + if (hasAdminToken) { + try { + const authUser = await identity.validateToken({ + token: adminUserAuthToken as string, + }) + + // we set this flag to true if the token is valid by current standards + // in the future we should remove this line + hasCurrentValidAdminToken = true + + if (authUser?.audience === 'admin') { + hasValidAdminToken = true + } else { + hasValidAdminToken = false + } + } catch (err) { + // noop so we leave hasValidAdminToken as undefined + } + } + + // check if has api token and if it is valid + const apiToken = context?.headers['vtex-api-apptoken'] as string + const appKey = context?.headers['vtex-api-appkey'] as string + const hasApiToken = !!(apiToken?.length && appKey?.length) + let hasValidApiToken + + if (hasApiToken) { + try { + const { token } = await identity.getToken({ + appkey: appKey, + apptoken: apiToken, + }) + + const authUser = await identity.validateToken({ + token, + }) + + if (authUser?.audience === 'admin') { + hasValidApiToken = true + } else { + hasValidApiToken = false + } + } catch (err) { + // noop so we leave hasValidApiToken as undefined + } + } + + // check if has store token and if it is valid + const hasStoreToken = !!storeUserAuthToken + let hasValidStoreToken + // this is used to check if the token is valid by current standards + let hasCurrentValidStoreToken = false + + if (hasStoreToken) { + try { + const authUser = await vtexId.getAuthenticatedUser( + storeUserAuthToken as string + ) + + if (authUser?.user) { + // we set this flag to true if the token is valid by current standards + // in the future we should remove this line + hasCurrentValidStoreToken = true + + const user = (await getActiveUserByEmail( + null, + { email: authUser?.user }, + context + )) as { roleId: string } | null + + if (user?.roleId) { + hasValidStoreToken = true + } else { + hasValidStoreToken = false + } + } else { + hasValidStoreToken = false + } + } catch (err) { + // noop so we leave hasValidStoreToken as undefined + } + } + + // now we emit a metric with all the collected data before we proceed + const operation = field?.astNode?.name?.value ?? context?.request?.url + const auditMetric = new AuthMetric( + context?.vtex?.account, + { + operation, + forwardedHost: context?.request?.headers[ + 'x-forwarded-host' + ] as string, + caller: context?.request?.headers['x-vtex-caller'] as string, + userAgent: context?.request?.headers['user-agent'] as string, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, + hasValidStoreToken, + }, + 'CheckUserAccessAudit' + ) + + sendAuthMetric(logger, auditMetric) + + if (!hasAdminToken && !hasStoreToken) { + logger.warn({ + message: 'CheckUserAccess: No token provided', + userAgent: context?.request?.headers['user-agent'], + vtexCaller: context?.request?.headers['x-vtex-caller'], + forwardedHost: context?.request?.headers['x-forwarded-host'], + operation, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, + }) + throw new AuthenticationError('No token was provided') + } + + if (!hasCurrentValidAdminToken && !hasCurrentValidStoreToken) { + logger.warn({ + message: `CheckUserAccess: Invalid token`, + userAgent: context?.request?.headers['user-agent'], + vtexCaller: context?.request?.headers['x-vtex-caller'], + forwardedHost: context?.request?.headers['x-forwarded-host'], + operation, + hasAdminToken, + hasValidAdminToken, + hasApiToken, + hasValidApiToken, + hasStoreToken, + hasValidStoreToken, + }) + throw new ForbiddenError('Unauthorized Access') + } return resolve(root, args, context, info) } diff --git a/node/metrics/auth.ts b/node/metrics/auth.ts index 2dda758..c2c675c 100644 --- a/node/metrics/auth.ts +++ b/node/metrics/auth.ts @@ -11,8 +11,11 @@ export interface AuthAuditMetric { role?: string permissions?: string[] hasAdminToken: boolean + hasValidAdminToken?: boolean hasStoreToken: boolean + hasValidStoreToken?: boolean hasApiToken: boolean + hasValidApiToken?: boolean } export class AuthMetric implements Metric { From ac08d94594c8f2f2c18a987b226386becc2b9016 Mon Sep 17 00:00:00 2001 From: Enzo Mercanti Date: Fri, 17 May 2024 16:20:11 -0300 Subject: [PATCH 2/4] chore: update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b73b4b..2800d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Improved metrics and logging for checkUserAccess and checkAdminAccess directives ## [1.40.4] - 2024-04-29 From bbe6a6c7bd2acb1bed4f639cef591c1b91b323a3 Mon Sep 17 00:00:00 2001 From: Enzo Mercanti Date: Fri, 17 May 2024 16:49:49 -0300 Subject: [PATCH 3/4] chore: refactor code --- node/directives/checkAdminAccess.ts | 85 +++++------------- node/directives/checkUserAccess.ts | 124 ++++++-------------------- node/directives/helper.ts | 133 ++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 164 deletions(-) create mode 100644 node/directives/helper.ts diff --git a/node/directives/checkAdminAccess.ts b/node/directives/checkAdminAccess.ts index 73ba34b..dd44b03 100644 --- a/node/directives/checkAdminAccess.ts +++ b/node/directives/checkAdminAccess.ts @@ -4,6 +4,7 @@ import type { GraphQLField } from 'graphql' import { defaultFieldResolver } from 'graphql' import sendAuthMetric, { AuthMetric } from '../metrics/auth' +import { validateAdminToken, validateApiToken } from './helper' export class CheckAdminAccess extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { @@ -17,76 +18,30 @@ export class CheckAdminAccess extends SchemaDirectiveVisitor { ) => { const { vtex: { adminUserAuthToken, storeUserAuthToken, logger }, - clients: { identity }, } = context - // check if has admin token and if it is valid - const hasAdminToken = !!adminUserAuthToken - let hasValidAdminToken - // this is used to check if the token is valid by current standards - let hasCurrentValidAdminToken = false + const { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken } = + await validateAdminToken(context, adminUserAuthToken as string) - if (hasAdminToken) { - try { - const authUser = await identity.validateToken({ - token: adminUserAuthToken as string, - }) + const { hasApiToken, hasValidApiToken } = await validateApiToken(context) - // we set this flag to true if the token is valid by current standards - // in the future we should remove this line - hasCurrentValidAdminToken = true - - if (authUser?.audience === 'admin') { - hasValidAdminToken = true - } else { - hasValidAdminToken = false - } - } catch (err) { - // noop so we leave hasValidAdminToken as undefined - } - } - - // check if has api token and if it is valid - const apiToken = context?.headers['vtex-api-apptoken'] as string - const appKey = context?.headers['vtex-api-appkey'] as string - const hasApiToken = !!(apiToken?.length && appKey?.length) - let hasValidApiToken - - if (hasApiToken) { - try { - const { token } = await identity.getToken({ - appkey: appKey, - apptoken: apiToken, - }) - - const authUser = await identity.validateToken({ - token, - }) - - if (authUser?.audience === 'admin') { - hasValidApiToken = true - } else { - hasValidApiToken = false - } - } catch (err) { - // noop so we leave hasValidApiToken as undefined - } - } - - // check if has store token but don't check if it is valid as we don't need it - const hasStoreToken = !!storeUserAuthToken + const hasStoreToken = !!storeUserAuthToken // we don't need to validate store token // now we emit a metric with all the collected data before we proceed const operation = field.astNode?.name?.value ?? context.request.url + const userAgent = context?.request?.headers['user-agent'] as string + const caller = context?.request?.headers['x-vtex-caller'] as string + const forwardedHost = context?.request?.headers[ + 'x-forwarded-host' + ] as string + const auditMetric = new AuthMetric( context?.vtex?.account, { operation, - forwardedHost: context?.request?.headers[ - 'x-forwarded-host' - ] as string, - caller: context?.request?.headers['x-vtex-caller'] as string, - userAgent: context?.request?.headers['user-agent'] as string, + forwardedHost, + caller, + userAgent, hasAdminToken, hasValidAdminToken, hasApiToken, @@ -101,9 +56,9 @@ export class CheckAdminAccess extends SchemaDirectiveVisitor { if (!hasAdminToken) { logger.warn({ message: 'CheckAdminAccess: No token provided', - userAgent: context?.request?.headers['user-agent'], - vtexCaller: context?.request?.headers['x-vtex-caller'], - forwardedHost: context?.request?.headers['x-forwarded-host'], + userAgent, + caller, + forwardedHost, operation, hasAdminToken, hasValidAdminToken, @@ -117,9 +72,9 @@ export class CheckAdminAccess extends SchemaDirectiveVisitor { if (!hasCurrentValidAdminToken) { logger.warn({ message: 'CheckAdminAccess: Invalid token', - userAgent: context?.request?.headers['user-agent'], - vtexCaller: context?.request?.headers['x-vtex-caller'], - forwardedHost: context?.request?.headers['x-forwarded-host'], + userAgent, + caller, + forwardedHost, operation, hasAdminToken, hasValidAdminToken, diff --git a/node/directives/checkUserAccess.ts b/node/directives/checkUserAccess.ts index 602b077..f8c233f 100644 --- a/node/directives/checkUserAccess.ts +++ b/node/directives/checkUserAccess.ts @@ -3,8 +3,12 @@ import type { GraphQLField } from 'graphql' import { defaultFieldResolver } from 'graphql' import { SchemaDirectiveVisitor } from 'graphql-tools' -import { getActiveUserByEmail } from '../resolvers/Queries/Users' import sendAuthMetric, { AuthMetric } from '../metrics/auth' +import { + validateAdminToken, + validateApiToken, + validateStoreToken, +} from './helper' export class CheckUserAccess extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { @@ -18,109 +22,31 @@ export class CheckUserAccess extends SchemaDirectiveVisitor { ) => { const { vtex: { adminUserAuthToken, storeUserAuthToken, logger }, - clients: { identity, vtexId }, } = context - // check if has admin token and if it is valid - const hasAdminToken = !!adminUserAuthToken - let hasValidAdminToken - // this is used to check if the token is valid by current standards - let hasCurrentValidAdminToken = false + const { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken } = + await validateAdminToken(context, adminUserAuthToken as string) - if (hasAdminToken) { - try { - const authUser = await identity.validateToken({ - token: adminUserAuthToken as string, - }) + const { hasApiToken, hasValidApiToken } = await validateApiToken(context) - // we set this flag to true if the token is valid by current standards - // in the future we should remove this line - hasCurrentValidAdminToken = true - - if (authUser?.audience === 'admin') { - hasValidAdminToken = true - } else { - hasValidAdminToken = false - } - } catch (err) { - // noop so we leave hasValidAdminToken as undefined - } - } - - // check if has api token and if it is valid - const apiToken = context?.headers['vtex-api-apptoken'] as string - const appKey = context?.headers['vtex-api-appkey'] as string - const hasApiToken = !!(apiToken?.length && appKey?.length) - let hasValidApiToken - - if (hasApiToken) { - try { - const { token } = await identity.getToken({ - appkey: appKey, - apptoken: apiToken, - }) - - const authUser = await identity.validateToken({ - token, - }) - - if (authUser?.audience === 'admin') { - hasValidApiToken = true - } else { - hasValidApiToken = false - } - } catch (err) { - // noop so we leave hasValidApiToken as undefined - } - } - - // check if has store token and if it is valid - const hasStoreToken = !!storeUserAuthToken - let hasValidStoreToken - // this is used to check if the token is valid by current standards - let hasCurrentValidStoreToken = false - - if (hasStoreToken) { - try { - const authUser = await vtexId.getAuthenticatedUser( - storeUserAuthToken as string - ) - - if (authUser?.user) { - // we set this flag to true if the token is valid by current standards - // in the future we should remove this line - hasCurrentValidStoreToken = true - - const user = (await getActiveUserByEmail( - null, - { email: authUser?.user }, - context - )) as { roleId: string } | null - - if (user?.roleId) { - hasValidStoreToken = true - } else { - hasValidStoreToken = false - } - } else { - hasValidStoreToken = false - } - } catch (err) { - // noop so we leave hasValidStoreToken as undefined - } - } + const { hasStoreToken, hasValidStoreToken, hasCurrentValidStoreToken } = + await validateStoreToken(context, storeUserAuthToken as string) // now we emit a metric with all the collected data before we proceed const operation = field?.astNode?.name?.value ?? context?.request?.url + const userAgent = context?.request?.headers['user-agent'] as string + const caller = context?.request?.headers['x-vtex-caller'] as string + const forwardedHost = context?.request?.headers[ + 'x-forwarded-host' + ] as string + const auditMetric = new AuthMetric( context?.vtex?.account, { operation, - forwardedHost: context?.request?.headers[ - 'x-forwarded-host' - ] as string, - caller: context?.request?.headers['x-vtex-caller'] as string, - userAgent: context?.request?.headers['user-agent'] as string, + forwardedHost, + caller, + userAgent, hasAdminToken, hasValidAdminToken, hasApiToken, @@ -136,9 +62,9 @@ export class CheckUserAccess extends SchemaDirectiveVisitor { if (!hasAdminToken && !hasStoreToken) { logger.warn({ message: 'CheckUserAccess: No token provided', - userAgent: context?.request?.headers['user-agent'], - vtexCaller: context?.request?.headers['x-vtex-caller'], - forwardedHost: context?.request?.headers['x-forwarded-host'], + userAgent, + caller, + forwardedHost, operation, hasAdminToken, hasValidAdminToken, @@ -152,9 +78,9 @@ export class CheckUserAccess extends SchemaDirectiveVisitor { if (!hasCurrentValidAdminToken && !hasCurrentValidStoreToken) { logger.warn({ message: `CheckUserAccess: Invalid token`, - userAgent: context?.request?.headers['user-agent'], - vtexCaller: context?.request?.headers['x-vtex-caller'], - forwardedHost: context?.request?.headers['x-forwarded-host'], + userAgent, + caller, + forwardedHost, operation, hasAdminToken, hasValidAdminToken, diff --git a/node/directives/helper.ts b/node/directives/helper.ts new file mode 100644 index 0000000..8b162d4 --- /dev/null +++ b/node/directives/helper.ts @@ -0,0 +1,133 @@ +import { getActiveUserByEmail } from '../resolvers/Queries/Users' + +export const validateAdminToken = async ( + context: Context, + adminUserAuthToken: string +): Promise<{ + hasAdminToken: boolean + hasValidAdminToken: boolean + hasCurrentValidAdminToken: boolean +}> => { + const { + clients: { identity }, + } = context + + // check if has admin token and if it is valid + const hasAdminToken = !!adminUserAuthToken + let hasValidAdminToken = false + // this is used to check if the token is valid by current standards + let hasCurrentValidAdminToken = false + + if (hasAdminToken) { + try { + const authUser = await identity.validateToken({ + token: adminUserAuthToken as string, + }) + + // we set this flag to true if the token is valid by current standards + // in the future we should remove this line + hasCurrentValidAdminToken = true + + if (authUser?.audience === 'admin') { + hasValidAdminToken = true + } else { + hasValidAdminToken = false + } + } catch (err) { + // noop so we leave hasValidAdminToken as false + } + } + + return { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken } +} + +export const validateApiToken = async ( + context: Context +): Promise<{ + hasApiToken: boolean + hasValidApiToken: boolean +}> => { + const { + clients: { identity }, + } = context + + // check if has api token and if it is valid + const apiToken = context?.headers['vtex-api-apptoken'] as string + const appKey = context?.headers['vtex-api-appkey'] as string + const hasApiToken = !!(apiToken?.length && appKey?.length) + let hasValidApiToken = false + + if (hasApiToken) { + try { + const { token } = await identity.getToken({ + appkey: appKey, + apptoken: apiToken, + }) + + const authUser = await identity.validateToken({ + token, + }) + + if (authUser?.audience === 'admin') { + hasValidApiToken = true + } else { + hasValidApiToken = false + } + } catch (err) { + // noop so we leave hasValidApiToken as false + } + } + + return { hasApiToken, hasValidApiToken } +} + +export const validateStoreToken = async ( + context: Context, + storeUserAuthToken: string +): Promise<{ + hasStoreToken: boolean + hasValidStoreToken: boolean + hasCurrentValidStoreToken: boolean +}> => { + const { + clients: { vtexId }, + } = context + + // check if has store token and if it is valid + const hasStoreToken = !!storeUserAuthToken + let hasValidStoreToken = false + // this is used to check if the token is valid by current standards + let hasCurrentValidStoreToken = false + + if (hasStoreToken) { + try { + const authUser = await vtexId.getAuthenticatedUser( + storeUserAuthToken as string + ) + + if (authUser?.user) { + // we set this flag to true if the token is valid by current standards + // in the future we should remove this line + hasCurrentValidStoreToken = true + + const user = (await getActiveUserByEmail( + null, + { email: authUser?.user }, + context + )) as { roleId: string } | null + + if (user?.roleId) { + hasValidStoreToken = true + } else { + hasValidStoreToken = false + } + } else { + hasValidStoreToken = false + } + } catch (err) { + // noop so we leave hasValidStoreToken as false + } + } + + return { hasStoreToken, hasValidStoreToken, hasCurrentValidStoreToken } +} From bfa67a27ccd3b314053fd3e70a0f4211a41076e9 Mon Sep 17 00:00:00 2001 From: Enzo Mercanti Date: Fri, 17 May 2024 16:55:12 -0300 Subject: [PATCH 4/4] chore: fix lint errors --- node/directives/helper.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/node/directives/helper.ts b/node/directives/helper.ts index 8b162d4..374db05 100644 --- a/node/directives/helper.ts +++ b/node/directives/helper.ts @@ -21,7 +21,7 @@ export const validateAdminToken = async ( if (hasAdminToken) { try { const authUser = await identity.validateToken({ - token: adminUserAuthToken as string, + token: adminUserAuthToken, }) // we set this flag to true if the token is valid by current standards @@ -30,8 +30,6 @@ export const validateAdminToken = async ( if (authUser?.audience === 'admin') { hasValidAdminToken = true - } else { - hasValidAdminToken = false } } catch (err) { // noop so we leave hasValidAdminToken as false @@ -70,8 +68,6 @@ export const validateApiToken = async ( if (authUser?.audience === 'admin') { hasValidApiToken = true - } else { - hasValidApiToken = false } } catch (err) { // noop so we leave hasValidApiToken as false @@ -101,9 +97,7 @@ export const validateStoreToken = async ( if (hasStoreToken) { try { - const authUser = await vtexId.getAuthenticatedUser( - storeUserAuthToken as string - ) + const authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken) if (authUser?.user) { // we set this flag to true if the token is valid by current standards @@ -118,11 +112,7 @@ export const validateStoreToken = async ( if (user?.roleId) { hasValidStoreToken = true - } else { - hasValidStoreToken = false } - } else { - hasValidStoreToken = false } } catch (err) { // noop so we leave hasValidStoreToken as false