Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
78 changes: 78 additions & 0 deletions docs/sdk/api/sdkMiddlewareAuth.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const client = createClient({
credentials: {
clientId: '123',
clientSecret: 'secret',
user: {
username: string;
password: string;
}
},
scopes: [
'view_products:test',
Expand Down Expand Up @@ -89,3 +93,77 @@ const client = createClient({
],
})
```

## `createAuthMiddlewareForPasswordFlow(options)`

Creates a [middleware](/sdk/Glossary.md#middleware) to handle authentication for the [Password Flow](http://dev.commercetools.com/http-api-authorization.html#password-flow) of the commercetools platform API.

#### Named arguments (options)

1. `host` *(String)*: the host of the OAuth API service
2. `projectKey` *(String)*: the key of the project to assign the default scope to
3. `credentials` *(Object)*: the client credentials for authentication (`clientId`, `clientSecret` `user`)
- The user field is an object containining `username` and `password`. [Sample below](#usage-example)
4. `scopes` *(Array)*: a list of [scopes](http://dev.commercetools.com/http-api-authorization.html#scopes) (default `manage_project:{projectKey}`) to assign to the OAuth token


#### Usage example

```js
import { createClient } from '@commercetools/sdk-client'
import { createAuthMiddlewareForPasswordFlow } from '@commercetools/sdk-middleware-auth'

const client = createClient({
middlewares: [
createAuthMiddlewareForPasswordFlow({
host: 'https://auth.commercetools.com',
projectKey: 'test',
credentials: {
clientId: '123',
clientSecret: 'secret',
},
scopes: [
'view_products:test',
'manage_orders:test',
],
}),
],
})
```

## `createAuthMiddlewareForAnonymousSessionFlow(options)`

Creates a [middleware](/sdk/Glossary.md#middleware) to handle authentication for the [Anonymous Session Flow](http://dev.commercetools.com/http-api-authorization.html#tokens-for-anonymous-sessions) of the commercetools platform API.

#### Named arguments (options)

1. `host` *(String)*: the host of the OAuth API service
2. `projectKey` *(String)*: the key of the project to assign the default scope to
3. `credentials` *(Object)*: the client credentials for authentication (`clientId`, `clientSecret`, `anonymousId`)
4. `scopes` *(Array)*: a list of [scopes](http://dev.commercetools.com/http-api-authorization.html#scopes) (default `manage_project:{projectKey}`) to assign to the OAuth token


#### Usage example

```js
import { createClient } from '@commercetools/sdk-client'
import { createAuthMiddlewareForAnonymousSessionFlow } from '@commercetools/sdk-middleware-auth'

const client = createClient({
middlewares: [
createAuthMiddlewareForAnonymousSessionFlow({
host: 'https://auth.commercetools.com',
projectKey: 'test',
credentials: {
clientId: '123',
clientSecret: 'secret',
anonymousId: 'unique-id-of-customer-not-required',
},
scopes: [
'view_products:test',
'manage_orders:test',
],
}),
],
})
```
1 change: 1 addition & 0 deletions integration-tests/sdk/customer-login.it.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('Customer Login', () => {
}])),
)
afterAll(() => clearData(apiConfig, 'customers'))

it('should log customer and fetch customer profile', () => {
const userConfig = {
...apiConfig,
Expand Down
37 changes: 35 additions & 2 deletions packages/sdk-middleware-auth/src/anonymous-session-flow.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
export default function createAuthMiddlewareForAnonymousSessionFlow () {
throw new Error('Middleware not implemented yet')
/* @flow */
import type {
AuthMiddlewareOptions,
Middleware,
MiddlewareRequest,
MiddlewareResponse,
Next,
Task,
} from 'types/sdk'

import { buildRequestForAnonymousSessionFlow } from './build-requests'
import authMiddlewareBase from './base-auth-flow'
import store from './utils'

export default function createAuthMiddlewareForAnonymousSessionFlow (
options: AuthMiddlewareOptions,
): Middleware {
const tokenCache = store({})
const pendingTasks: Array<Task> = []

const requestState = store(false)
return (next: Next) => (
request: MiddlewareRequest,
response: MiddlewareResponse,
) => {
const params = {
request,
response,
...buildRequestForAnonymousSessionFlow(options),
pendingTasks,
requestState,
tokenCache,
}
authMiddlewareBase(params, next)
}
}
2 changes: 0 additions & 2 deletions packages/sdk-middleware-auth/src/base-auth-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import type {
Task,
AuthMiddlewareBaseOptions,
} from 'types/sdk'

/* global fetch */
import 'isomorphic-fetch'


export default function authMiddlewareBase ({
request,
response,
Expand Down
20 changes: 17 additions & 3 deletions packages/sdk-middleware-auth/src/build-requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export function buildRequestForPasswordFlow (
const scope = (options.scopes || []).join(' ')
const scopeStr = scope ? `&scope=${scope}` : ''


const basicAuth = new Buffer(`${clientId}:${clientSecret}`).toString('base64')
// This is mostly useful for internal testing purposes to be able to check
// other oauth endpoints.
Expand All @@ -94,6 +93,21 @@ export function buildRequestForRefreshTokenFlow () {
// TODO
}

export function buildRequestForAnonymousSessionFlow () {
// TODO
export function buildRequestForAnonymousSessionFlow (
options: AuthMiddlewareOptions,
): BuiltRequestParams {
if (!options)
throw new Error('Missing required options')

if (!options.projectKey)
throw new Error('Missing required option (projectKey)')
const pKey = options.projectKey
// eslint-disable-next-line no-param-reassign
options.oauthUri = options.oauthUri || `/oauth/${pKey}/anonymous/token`
const result = buildRequestForClientCredentialsFlow(options)

if (options.credentials.anonymousId)
result.body += `&anonymous_id=${options.credentials.anonymousId}`

return { ...result }
}
63 changes: 63 additions & 0 deletions packages/sdk-middleware-auth/test/anonymous-session-flow.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
createAuthMiddlewareForAnonymousSessionFlow,
} from '../src'

import authMiddlewareBase from '../src/base-auth-flow'

jest.mock('../src/base-auth-flow')

function createTestRequest (options) {
return {
url: '',
method: 'GET',
body: null,
headers: {},
...options,
}
}

function createTestMiddlewareOptions (options) {
return {
host: 'https://auth.commercetools.co',
projectKey: 'foo',
credentials: {
clientId: '123',
clientSecret: 'secret',
anonymousId: 'secretme',
},
...options,
}
}

describe('Anonymous Session Flow', () => {
it('should call the base-auth-flow method with the right params', () =>
new Promise((resolve, reject) => {
authMiddlewareBase.mockImplementation((params, next) => {
next(params) // makes it easy to test what was passed in
})
const request = createTestRequest()
const response = {
resolve,
reject,
}
const next = (actualParams) => {
expect(actualParams.request).toEqual(request)
expect(actualParams.response).toEqual(response)
expect(actualParams.pendingTasks).toEqual([])
expect(actualParams.url).toBe(
'https://auth.commercetools.co/oauth/foo/anonymous/token',
)
expect(actualParams.basicAuth).toBe('MTIzOnNlY3JldA==')
expect(authMiddlewareBase).toHaveBeenCalledTimes(1)
jest.unmock('../src/base-auth-flow')
resolve()
}
const middlewareOptions = createTestMiddlewareOptions()
const authMiddleware = createAuthMiddlewareForAnonymousSessionFlow(
middlewareOptions,
)

authMiddleware(next)(request, response)
}),
)
})
56 changes: 51 additions & 5 deletions packages/sdk-middleware-auth/test/build-requests.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
buildRequestForClientCredentialsFlow,
buildRequestForPasswordFlow,
// buildRequestForRefreshTokenFlow,
// buildRequestForAnonymousSessionFlow,
buildRequestForAnonymousSessionFlow,
} from '../src/build-requests'
import { scopes } from '../src'

Expand All @@ -28,8 +28,10 @@ function createTestOptions (options) {
}

describe('buildRequestForPasswordFlow', () => {
const body = oneLineTrim`grant_type=password&
username=foobar&password=verysecurepassword&
const body = oneLineTrim`
grant_type=password&
username=foobar&
password=verysecurepassword&
scope=${allScopes.join(' ')}
`
it('build request values with all the given options', () => {
Expand Down Expand Up @@ -116,8 +118,8 @@ describe('buildRequestForPasswordFlow', () => {
it('validate required option (username, password)', () => {
const options = createTestOptions({
credentials: {
clientId: 'yeah',
clientSecret: 'yo',
clientId: 'foo',
clientSecret: 'baz',
user: {
username: 'bar',
},
Expand Down Expand Up @@ -231,3 +233,47 @@ describe('buildRequestForClientCredentialsFlow', () => {
).toThrowError('Missing required credentials (clientId, clientSecret)')
})
})

describe('buildRequestForAnonymousSessionFlow', () => {
it('build request values with all the given options', () => {
const options = createTestOptions()
expect(buildRequestForAnonymousSessionFlow(options)).toEqual({
basicAuth: 'MTIzOnNlY3JldA==',
url: 'http://localhost:8080/oauth/test/anonymous/token',
body: `grant_type=client_credentials&scope=${allScopes.join(' ')}`,
})
})

it('validate required options', () => {
expect(
() => buildRequestForAnonymousSessionFlow(),
).toThrowError('Missing required options')
})

it('validate required option (projectKey)', () => {
const options = createTestOptions({
projectKey: undefined,
})
expect(
() => buildRequestForAnonymousSessionFlow(options),
).toThrowError('Missing required option (projectKey)')
})

it('should add anonymousId if passed in', () => {
const mockCred = {
clientId: '123',
clientSecret: 'secret',
anonymousId: 'youdontknowme',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • clientId: foo
  • clientSecret: bar
  • anonymousId: baz

}
const options = createTestOptions({ credentials: mockCred })
expect(buildRequestForAnonymousSessionFlow(options)).toEqual({
basicAuth: 'MTIzOnNlY3JldA==',
url: 'http://localhost:8080/oauth/test/anonymous/token',
body: oneLineTrim`
grant_type=client_credentials&
scope=${allScopes.join(' ')}&
anonymous_id=youdontknowme
`,
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ function createTestMiddlewareOptions (options) {
}

describe('Client Crentials Flow', () => {
afterAll(() => {
jest.unmock('../src/base-auth-flow')
})
it('should call the base-auth-flow method with the right params', () =>
new Promise((resolve, reject) => {
authMiddlewareBase.mockImplementation((params, next) => {
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk-middleware-auth/test/password-flow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {

import authMiddlewareBase from '../src/base-auth-flow'

// required to be at the root because Jest hoists it above all requires,
// if in any method like `beforeAll`,
// it will be hoisted within the scope of that method
jest.mock('../src/base-auth-flow')

function createTestRequest (options) {
Expand Down Expand Up @@ -32,7 +35,11 @@ function createTestMiddlewareOptions (options) {
}
}


describe('Password Flow', () => {
afterAll(() => {
jest.unmock('../src/base-auth-flow')
})
it('should call the base-auth-flow method with the right params', () =>
new Promise((resolve, reject) => {
authMiddlewareBase.mockImplementation((params, next) => {
Expand Down
2 changes: 2 additions & 0 deletions types/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ export type Middleware = (next: Dispatch) => Dispatch;
export type ClientOptions = {
middlewares: Array<Middleware>;
}

export type AuthMiddlewareOptions = {
host: string;
projectKey: string;
credentials: {
clientId: string;
clientSecret: string;
anonymousId?: string;
};
scopes: Array<string>;
// For internal usage only
Expand Down