Skip to content

Commit

Permalink
Merge pull request #121 from sharetribe/auth-with-idp
Browse files Browse the repository at this point in the history
Auth with idp
  • Loading branch information
lyyder authored Oct 15, 2020
2 parents 9a3c1a4 + bd101ac commit 7c9c8aa
Show file tree
Hide file tree
Showing 13 changed files with 556 additions and 139 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased] - xxxx-xx-xx

## [v1.13.0] - 2020-10-15

### Added

- New endpoints [#121](https://github.com/sharetribe/flex-sdk-js/pull/121)
- `sdk.loginWithIdp(/* ... */)`
- `sdk.currentUser.createWithIdp(/* ... */)`

## [v1.12.0] - 2020-08-12

### Added
Expand Down
402 changes: 298 additions & 104 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.

70 changes: 45 additions & 25 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ the user is already logged in or not.
Logs in the user and returns a Promise.

The session information will be saved to the SDK instance when the
Promise is resolved. Subsequest requests will be made as the logged in
Promise is resolved. Subsequent requests will be made as the logged in
user.

## Logout
Expand All @@ -30,12 +30,23 @@ store](./token-store.md#memory-store).

Returns a Promise with an Object as a value. The object may contain two fields:

* `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
- `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 `isAnonymous` equals
`false`.

## Login with IdP

**`sdk.loginWithIdp({ idpId: string, idpClientId: string, idpToken: string }) : Promise`**

Logs in the user with information from an identity provider (e.g. Facebook) and returns a Promise.
User can be authenticated if the `idpToken` can be verified and an identity provider account resolved from the token is associated with a Flex account or if an email address resolved from the token matches a verified email of a Flex account.

The session information will be saved to the SDK instance when the
Promise is resolved. Subsequent requests will be made as the logged in
user.

**Example:**

```js
Expand All @@ -59,42 +70,52 @@ example, to know the name of the logged in user, you need to call
## Authentication example
Here's a full example how to log user in and out and determine the
current authentication status.
**Example:**
```js
const isLoggedIn = authInfo => authInfo && authInfo.isAnonymous === false;
const isLoggedIn = (authInfo) => authInfo && authInfo.isAnonymous === false;

sdk.authInfo().then(authInfo => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`)
sdk
.authInfo()
.then((authInfo) => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`);
// prints: "Logged in: false"

return sdk.login({ username: '[email protected]', password: 'test-secret' });
}).then(loginRes => {
return sdk.login({
username: "[email protected]",
password: "test-secret",
});
})
.then((loginRes) => {
console.log("Login successful!");

return sdk.authInfo();
}).then(authInfo => {
})
.then((authInfo) => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`);
// prints: "Logged in: true"

return sdk.currentUser.show();
}).then(userRes => {
})
.then((userRes) => {
const profile = userRes.data.data.attributes.profile;
console.log(`Current user: ${profile.firstName} ${profile.lastName}`);

return sdk.logout();
}).then(logoutRes => {
})
.then((logoutRes) => {
console.log("Logged out!");

return sdk.authInfo();
}).then(authInfo => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`)
})
.then((authInfo) => {
console.log(`Logged in: ${isLoggedIn(authInfo)}`);
// prints: "Logged in: false"
}).catch(res => {
})
.catch((res) => {
// An error occurred
console.log(`Request failed with status: ${res.status} ${res.statusText}`);
});
Expand All @@ -117,17 +138,16 @@ SDK with a token store that holds an access token.
```js
const sdk = sharetribeSdk.createInstance({
clientId: 'a client ID',
clientSecret: 'a client secret',
clientId: "a client ID",
clientSecret: "a client secret",
tokenStore: sharetribeSdk.tokenStore.memoryStore(),
})
});

sdk.login({ username: '[email protected]', password: 'test-secret' });
sdk.login({ username: "[email protected]", password: "test-secret" });

sdk.exchangeToken().then(res => {
console.log('Trusted token: ', res.data);
sdk.exchangeToken().then((res) => {
console.log("Trusted token: ", res.data);
});

```
## Seeing occasional 401 errors?
Expand All @@ -137,12 +157,12 @@ Don't worry! That's part of the normal operations.
Flex API uses two authentication tokens for each session:
* *Access token* that is used to authenticate the user. Valid only a
- _Access token_ that is used to authenticate the user. Valid only a
short amount of time.
* *Refresh token* that is used to issue a fresh authentication
- _Refresh token_ that is used to issue a fresh authentication
token. Long lived.
When *access token* expires, you will see a 401 error in browser's Web
When _access token_ expires, you will see a 401 error in browser's Web
Console. The SDK will handle this and automatically issue new fresh
authentication token and retry the request. This all happens under the
hood and you don't need to worry about it. SDK will do the heavy
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.12.0",
"version": "1.13.0",
"description": "Sharetribe Flex SDK for JavaScript",
"main": "build/sharetribe-flex-sdk-node.js",
"browser": "build/sharetribe-flex-sdk-web.js",
Expand Down
2 changes: 2 additions & 0 deletions src/fake/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ const defaultHandler = (config, resolve, reject, tokenStore) => {
return requireAuth(config, reject, tokenStore).then(() =>
auth.revoke(config, resolve, reject, tokenStore)
);
case 'fake-adapter://fake-api/v1/auth/auth_with_idp':
return auth.authWithIdp(config, resolve, reject, tokenStore);
default:
throw new Error(`Not implemented to Fake adapter: ${config.url}`);
}
Expand Down
24 changes: 24 additions & 0 deletions src/fake/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,27 @@ export const token = (config, resolve, reject, fakeTokenStore) => {
__additionalTestInfo: { formData },
});
};

export const authWithIdp = (config, resolve, reject, fakeTokenStore) => {
const formData = parseFormData(config.data);
const { idpId, idpClientId, idpToken } = formData;
let res;

if (formData.client_id === '08ec69f6-d37e-414d-83eb-324e94afddf0') {
res = fakeTokenStore.createTokenWithIdp(idpId, idpClientId, idpToken);
}

if (res) {
return resolve({ data: JSON.stringify(res) });
}

return reject({
status: 401,
statusText: 'Unauthorized',
data: 'Unauthorized',

// Add additional information to help debugging when testing.
// This key is NOT returned by the real API.
__additionalTestInfo: { formData },
});
};
38 changes: 38 additions & 0 deletions src/fake/token_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ const createTokenStore = () => {
{ code: 'flex-authorization-code', username: '[email protected]' },
];

const knownIdpTokens = [
{
id: 'facebook',
token: 'idp-token',
clientId: 'idp-client-id',
username: '[email protected]',
},
];

// Private

const generateAnonAccessToken = () => {
Expand Down Expand Up @@ -106,6 +115,34 @@ const createTokenStore = () => {
return token.token;
};

const createTokenWithIdp = (idpId, idpClientId, idpToken) => {
const knownIdpToken = _.find(
knownIdpTokens,
({ id, token, clientId }) => id === idpId && token === idpToken && clientId === idpClientId
);

if (!knownIdpToken) {
return null;
}

const { username } = knownIdpToken;
const token = {
token: {
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 exchangeToken = accessToken => {
const currentToken = _.find(
tokens,
Expand Down Expand Up @@ -182,6 +219,7 @@ const createTokenStore = () => {
createAnonToken,
createTokenWithCredentials,
createTokenWithAuthorizationCode,
createTokenWithIdp,
exchangeToken,
freshToken,
revokeRefreshToken,
Expand Down
17 changes: 17 additions & 0 deletions src/interceptors/add_idp_client_id_to_params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
Read `idpClientId` from `ctx` and add it to `params`
Changes to `ctx`:
- add `params.idpClientId`
*/
export default class AddIdpClientIdToParams {
enter({ params, ...ctx }) {
const { idpClientId } = params;
return {
...ctx,
idpClientId,
params: { ...params, idp_client_id: idpClientId },
};
}
}
17 changes: 17 additions & 0 deletions src/interceptors/add_idp_id_to_params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
Read `idpId` from `ctx` and add it to `params`
Changes to `ctx`:
- add `params.idpId`
*/
export default class AddIdpIdToParams {
enter({ params, ...ctx }) {
const { idpId } = params;
return {
...ctx,
idpId,
params: { ...params, idp_id: idpId },
};
}
}
17 changes: 17 additions & 0 deletions src/interceptors/add_idp_token_to_params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
Read `idpToken` from `ctx` and add it to `params`
Changes to `ctx`:
- add `params.idpToken`
*/
export default class AddIdpTokenToParams {
enter({ params, ...ctx }) {
const { idpToken } = params;
return {
...ctx,
idpToken,
params: { ...params, idp_token: idpToken },
};
}
}
Loading

0 comments on commit 7c9c8aa

Please sign in to comment.