Skip to content

Commit

Permalink
Merge pull request #107 from sharetribe/authorization-code
Browse files Browse the repository at this point in the history
Login using an authorization code
  • Loading branch information
lyyder authored Feb 12, 2020
2 parents 5efa224 + f0a4e47 commit 93b122e
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 206 deletions.
24 changes: 20 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,35 @@ adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased] - xxxx-xx-xx

## [v1.9.0] - 2020-02-12

### Added

- Support for logging in using an authorization code
[#107](https://github.com/sharetribe/flex-sdk-js/pull/107)

### Changed

- `sdk.authInfo` return value has been updated
[#107](https://github.com/sharetribe/flex-sdk-js/pull/107). The `grantType`
attribute has been deprecated and now the returned attributes are:
- `isAnonymous`: a boolean denoting if the currently stored token only allows
public read access
- `scopes`: an array containing the scopes associated with the access token

## [v1.8.0] - 2019-12-20

### Changed

- Add endpoint [#105](https://github.com/sharetribe/flex-sdk-js/pull/105)
- `sdk.stripeAccount.fetch(/* ... */)`
- Add endpoint [#105](https://github.com/sharetribe/flex-sdk-js/pull/105)
- `sdk.stripeAccount.fetch(/* ... */)`

## [v1.7.0] - 2019-12-05

### Changed

- Remove endpoint [#104](https://github.com/sharetribe/flex-sdk-js/pull/103)
- `sdk.stripeAccountData.fetch(/* ... */)`
- Remove endpoint [#104](https://github.com/sharetribe/flex-sdk-js/pull/103)
- `sdk.stripeAccountData.fetch(/* ... */)`

## [v1.6.0] - 2019-12-05

Expand Down
392 changes: 251 additions & 141 deletions build/sharetribe-flex-sdk-node.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions build/sharetribe-flex-sdk-web.js

Large diffs are not rendered by default.

20 changes: 7 additions & 13 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,19 @@ store](./token-store.md#memory-store).

**`sdk.authInfo() : Promise(Object)`**

Returns a Promise with an Object as a value. The object may contain a
`grantType` field with either `'client_credentials'`,
`'refresh_token'` as a value. The different values have the following
meanings:
Returns a Promise with an Object as a value. The object may contain two fields:

* No grant type: user hasn't yet authenticated itself (i.e. hasn't
done any requests to the API).
* Grant type `'client_credentials'`: user has authenticated as an
anonymous user (i.e. has not logged in)
* Grant type `'refresh_token'`: user has logged in.
* `scopes`: an array containing the scopes associated with the currently stored token
* `isAnonymous`: a boolean denoting if the currently stored token only allows public read access

To determine if the user is logged in, check if `grantType` equals
`'refresh_token'`.
To determine if the user is logged in, check if `isAnonymous` equals
`false`.

**Example:**

```js
sdk.authInfo().then(authInfo => {
if (authInfo && authInfo.grantType === 'refresh_token') {
if (authInfo && authInfo.isAnonymous === false) {
console.log("User is logged in.");
} else {
console.log("User is NOT logged in.")
Expand All @@ -72,7 +66,7 @@ current authentication status.
**Example:**
```js
const isLoggedIn = authInfo => authInfo && authInfo.grantType === 'refresh_token';
const isLoggedIn = authInfo => authInfo && authInfo.isAnonymous === false;

sdk.authInfo().then(authInfo => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sharetribe-flex-sdk",
"version": "1.8.0",
"version": "1.9.0",
"description": "Sharetribe Flex SDK for JavaScript",
"main": "build/sharetribe-flex-sdk-node.js",
"browser": "build/sharetribe-flex-sdk-web.js",
Expand Down
8 changes: 5 additions & 3 deletions src/fake/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const revoke = (config, resolve, reject, tokenStore) => {

if (formData.token) {
if (tokenStore) {
const revoked = tokenStore.revokePasswordToken(formData.token);
const revoked = tokenStore.revokeRefreshToken(formData.token);

if (revoked.length) {
return resolve({ data: { action: 'revoked' } });
Expand All @@ -31,9 +31,11 @@ export const token = (config, resolve, reject, fakeTokenStore) => {
if (formData.grant_type === 'client_credentials') {
res = fakeTokenStore.createAnonToken();
} else if (formData.grant_type === 'password') {
res = fakeTokenStore.createPasswordToken(formData.username, formData.password);
res = fakeTokenStore.createTokenWithCredentials(formData.username, formData.password);
} else if (formData.grant_type === 'authorization_code') {
res = fakeTokenStore.createTokenWithAuthorizationCode(formData.code);
} else if (formData.grant_type === 'refresh_token') {
res = fakeTokenStore.freshPasswordToken(formData.refresh_token);
res = fakeTokenStore.freshToken(formData.refresh_token);
}
}

Expand Down
91 changes: 68 additions & 23 deletions src/fake/token_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@ import _ from 'lodash';

const createTokenStore = () => {
const tokens = [];
let anonAccessCount = 0;
let passwordAccessCount = 0;
let passwordRefreshCount = 0;
let anonAccessTokenCount = 0;
let accessTokenCount = 0;
let refreshTokenCount = 0;

const knownUsers = [['[email protected]', 'secret-joe']];

const knownAuthorizationCodes = [
{ code: 'flex-authorization-code', username: '[email protected]' },
];

// Private

const generateAnonAccessToken = () => {
anonAccessCount += 1;
return `anonymous-access-${anonAccessCount}`;
anonAccessTokenCount += 1;
return `anonymous-access-${anonAccessTokenCount}`;
};

const generatePasswordAccessToken = (username, password) => {
passwordAccessCount += 1;
return `${username}-${password}-access-${passwordAccessCount}`;
const generateAccessToken = username => {
accessTokenCount += 1;
return `${username}-access-${accessTokenCount}`;
};

const generatePasswordRefreshToken = (username, password) => {
passwordRefreshCount += 1;
return `${username}-${password}-refresh-${passwordRefreshCount}`;
const generateRefreshToken = username => {
refreshTokenCount += 1;
return `${username}-refresh-${refreshTokenCount}`;
};

// Public
Expand All @@ -45,14 +49,15 @@ const createTokenStore = () => {
access_token: generateAnonAccessToken(),
token_type: 'bearer',
expires_in: 86400,
scope: 'public-read',
},
};
tokens.push(token);

return token.token;
};

const createPasswordToken = (username, password) => {
const createTokenWithCredentials = (username, password) => {
const user = _.find(knownUsers, u => _.isEqual(u, [username, password]));

if (!user) {
Expand All @@ -61,14 +66,39 @@ const createTokenStore = () => {

const token = {
token: {
access_token: generatePasswordAccessToken(username, password),
refresh_token: generatePasswordRefreshToken(username, password),
access_token: generateAccessToken(username),
refresh_token: generateRefreshToken(username),
token_type: 'bearer',
expires_in: 86400,
scope: 'user',
},
user: {
username,
},
};
tokens.push(token);

return token.token;
};

const createTokenWithAuthorizationCode = authorizationCode => {
const knownCode = _.find(knownAuthorizationCodes, ({ code }) => code === authorizationCode);

if (!knownCode) {
return null;
}

const { username } = knownCode;
const token = {
token: {
access_token: generateAccessToken(username),
refresh_token: generateRefreshToken(username),
token_type: 'bearer',
expires_in: 86400,
scope: 'user:limited',
},
user: {
username,
password,
},
};
tokens.push(token);
Expand All @@ -88,25 +118,40 @@ const createTokenStore = () => {
});
};

const revokePasswordToken = refreshToken =>
const revokeRefreshToken = refreshToken =>
_.remove(tokens, t => t.token.refresh_token === refreshToken);

const freshPasswordToken = refreshToken => {
const existingToken = revokePasswordToken(refreshToken);
const freshToken = refreshToken => {
const existingToken = revokeRefreshToken(refreshToken);

if (existingToken.length) {
const { username, password } = existingToken[0].user;
return createPasswordToken(username, password);
const { username } = existingToken[0].user;

const token = {
token: {
access_token: generateAccessToken(username),
refresh_token: generateRefreshToken(username),
token_type: 'bearer',
expires_in: 86400,
},
user: {
username,
},
};
tokens.push(token);

return token.token;
}

return null;
};

return {
createAnonToken,
createPasswordToken,
freshPasswordToken,
revokePasswordToken,
createTokenWithCredentials,
createTokenWithAuthorizationCode,
freshToken,
revokeRefreshToken,
validToken,
expireAccessToken,
};
Expand Down
23 changes: 23 additions & 0 deletions src/interceptors/add_grant_type_to_params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
See what credentials (`username`, `password`, and `authorizationCode`) are
passed in params and set the grant_type based on those.
Changes to `ctx`:
- add `params.grant_type`
*/
export default class AddGrantTypeToParams {
enter({ params, ...ctx }) {
const { username, password, code } = params;

if (username && password) {
return { ...ctx, params: { grant_type: 'password', ...params } };
}

if (code) {
return { ...ctx, params: { grant_type: 'authorization_code', ...params } };
}

return { ...ctx, params: { ...params } };
}
}
18 changes: 18 additions & 0 deletions src/interceptors/add_scope_to_params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
Set the scope for a token request based on the params.
Changes to `ctx`:
- add `params.scope`
*/
export default class AddScopeToParams {
enter({ params, ...ctx }) {
const { username, password } = params;

if (username && password) {
return { ...ctx, params: { scope: 'user', ...params } };
}

return { ...ctx, params: { ...params } };
}
}
18 changes: 14 additions & 4 deletions src/interceptors/auth_info.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/**
Reads current authentication token from the tokenStore and returns
information whether the user is currently logged in with anon token
or password token.
the following information:
- scopes: list of scopes associated with the access token in store
- isAnonymous: boolean value indicating if the access token only grants
access to publicly read data from API
Changes to `ctx`:
Expand All @@ -17,9 +21,15 @@ export default class AuthInfo {
.then(tokenStore.getToken)
.then(storedToken => {
if (storedToken) {
const grantType = storedToken.refresh_token ? 'refresh_token' : 'client_credentials';
const tokenScope = storedToken.scope;
const scopes = tokenScope.split(' ');
const isAnonymous = tokenScope === 'public-read';

// Deprecated attribute, maintained here for client implementations
// that rely on this attribute
const grantType = isAnonymous ? 'client_credentials' : 'refresh_token';

return { ...ctx, res: { grantType } };
return { ...ctx, res: { scopes, isAnonymous, grantType } };
}

return { ...ctx, res: {} };
Expand Down
6 changes: 4 additions & 2 deletions src/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import SaveToken from './interceptors/save_token';
import FetchAuthTokenFromApi from './interceptors/fetch_auth_token_from_api';
import FetchAuthTokenFromStore from './interceptors/fetch_auth_token_from_store';
import AddClientIdToParams from './interceptors/add_client_id_to_params';
import AddGrantTypeToParams from './interceptors/add_grant_type_to_params';
import AddScopeToParams from './interceptors/add_scope_to_params';
import AuthInfo from './interceptors/auth_info';
import defaultParams from './interceptors/default_params';
import MultipartRequest from './interceptors/multipart_request';
import TransitRequest from './interceptors/transit_request';
import TransitResponse from './interceptors/transit_response';
Expand Down Expand Up @@ -488,8 +489,9 @@ const authenticateInterceptors = [
];

const loginInterceptors = [
defaultParams({ grant_type: 'password', scope: 'user' }),
new AddClientIdToParams(),
new AddGrantTypeToParams(),
new AddScopeToParams(),
new SaveToken(),
new AddAuthTokenResponse(),
];
Expand Down
Loading

0 comments on commit 93b122e

Please sign in to comment.