Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/clients/external/ID.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ID } from './ID'

jest.mock('./ExternalClient', () => ({
ExternalClient: class {
protected context: any
protected http: any
protected options: any

constructor (baseURL: string, context: any, options: any) {
this.context = context
this.http = { get: jest.fn() }
this.options = options
}
},
}))

describe('ID client', () => {
const context: any = {
account: 'storecomponents',
authToken: 'auth-token',
}

const createClientWithMockedGet = () => {
const client = new ID(context)
const get = jest.fn().mockResolvedValue({ authenticationToken: 'temporary-token' })

;(client as any).http = { get }

return { client, get }
}

test('sets the account as a default query parameter', () => {
const client = new ID(context)

expect((client as any).options.params).toEqual({
an: context.account,
})
})

test('preserves custom default query parameters', () => {
const client = new ID(context, {
params: {
locale: 'en-US',
},
})

expect((client as any).options.params).toEqual({
an: context.account,
locale: 'en-US',
})
})

test('requests temporary tokens without overriding default account params', async () => {
const { client, get } = createClientWithMockedGet()

await client.getTemporaryToken()

expect(get).toHaveBeenCalledWith('/start', expect.not.objectContaining({
params: expect.anything(),
}))
})

test('sends account on email code requests', async () => {
const { client, get } = createClientWithMockedGet()

await client.sendCodeToEmail('authentication-token', 'user@example.com')

expect(get).toHaveBeenCalledWith('/accesskey/send', expect.objectContaining({
params: {
authenticationToken: 'authentication-token',
email: 'user@example.com',
},
}))
})

test('sends account on access key validation requests', async () => {
const { client, get } = createClientWithMockedGet()

await client.getEmailCodeAuthenticationToken('authentication-token', 'user@example.com', '123456')

expect(get).toHaveBeenCalledWith('/accesskey/validate', expect.objectContaining({
params: {
accesskey: '123456',
authenticationToken: 'authentication-token',
login: 'user@example.com',
},
}))
})

test('sends account on password validation requests', async () => {
const { client, get } = createClientWithMockedGet()

await client.getPasswordAuthenticationToken('authentication-token', 'user@example.com', 'password')

expect(get).toHaveBeenCalledWith('/classic/validate', expect.objectContaining({
params: {
authenticationToken: 'authentication-token',
login: 'user@example.com',
password: 'password',
},
}))
})
})
9 changes: 8 additions & 1 deletion src/clients/external/ID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ const endpoint = (env: string) => {

export class ID extends ExternalClient {
constructor (context: IOContext, opts?: InstanceOptions) {
super(endpoint(VTEXID_ENDPOINTS.STABLE), context, opts)
super(endpoint(VTEXID_ENDPOINTS.STABLE), context, {
...opts,
params: {
an: context.account,
...opts?.params,
},
})
}

public getTemporaryToken = (tracingConfig?: RequestTracingConfig) => {
Expand Down Expand Up @@ -64,6 +70,7 @@ export class ID extends ExternalClient {
...tracingConfig?.tracing,
}})
}

}

interface TemporaryToken {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import axios from 'axios'

import { Auth } from './Auth'

jest.mock('axios', () => ({
__esModule: true,
default: {
request: jest.fn(),
},
}))

describe('Auth directive', () => {
class TestAuth extends Auth {
constructor (args: any) {
super({
args,
context: {},
name: 'auth',
schema: {} as any,
visitedType: {} as any,
})
}
}

beforeEach(() => {
jest.clearAllMocks()
})

test('sends account when validating the VTEX ID token', async () => {
const request = axios.request as jest.Mock
request
.mockResolvedValueOnce({ data: { account: 'storecomponents', user: 'user@example.com' } })
.mockResolvedValueOnce({ data: true })

const field = {
resolve: jest.fn().mockResolvedValue('resolved'),
} as any
const directive = new TestAuth({
productCode: 'product-code',
resourceCode: 'resource-code',
scope: 'PRIVATE',
})
const ctx = {
cookies: {
get: jest.fn().mockReturnValue('vtex-id-token'),
},
get: jest.fn(),
vtex: {
account: 'storecomponents',
authToken: 'auth-token',
},
} as any

directive.visitFieldDefinition(field)

await field.resolve(null, {}, ctx, {})

const expectedUrl = 'vtexid.vtex.com.br/api/vtexid/pub/authenticated/user?authToken=vtex-id-token&an=storecomponents'

expect(request).toHaveBeenNthCalledWith(1, expect.objectContaining({
headers: expect.objectContaining({
'X-VTEX-Proxy-To': `https://${expectedUrl}`,
}),
url: `http://${expectedUrl}`,
}))
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ interface VtexIdParsedToken {
account: string
}

async function parseIdToken(authToken: string, vtexIdToken: string): Promise<VtexIdParsedToken | void> {
const url = `vtexid.vtex.com.br/api/vtexid/pub/authenticated/user?authToken=${vtexIdToken}`
async function parseIdToken(authToken: string, vtexIdToken: string, account: string): Promise<VtexIdParsedToken | void> {
const url = `vtexid.vtex.com.br/api/vtexid/pub/authenticated/user?authToken=${encodeURIComponent(vtexIdToken)}&an=${encodeURIComponent(account)}`
const req = await axios.request({
headers: {
'Accept': 'application/json',
Expand Down Expand Up @@ -51,7 +51,7 @@ async function auth (ctx: ServiceContext, authArgs: AuthDirectiveArgs): Promise<
throw new AuthenticationError('VtexIdclientAutCookie not found.')
}

const parsedToken = await parseIdToken(ctx.vtex.authToken, vtexIdToken)
const parsedToken = await parseIdToken(ctx.vtex.authToken, vtexIdToken, ctx.vtex.account)
if (!parsedToken || parsedToken.account !== ctx.vtex.account) {
throw new AuthenticationError('Could not find user specified by VtexIdclientAutCookie.')
}
Expand All @@ -69,7 +69,7 @@ async function auth (ctx: ServiceContext, authArgs: AuthDirectiveArgs): Promise<
}

function parseArgs (authArgs: AuthDirectiveArgs): AuthDirectiveArgs {
if (authArgs.scope == 'PUBLIC') {
if (authArgs.scope === 'PUBLIC') {
return authArgs
}

Expand All @@ -84,7 +84,7 @@ export class Auth extends SchemaDirectiveVisitor {
const {resolve = defaultFieldResolver} = field
field.resolve = async (root, args, ctx, info) => {
const authArgs = parseArgs(this.args as AuthDirectiveArgs)
if (!authArgs.scope || authArgs.scope == 'PRIVATE') {
if (!authArgs.scope || authArgs.scope === 'PRIVATE') {
await auth(ctx, authArgs)
}
return resolve(root, args, ctx, info)
Expand Down