This repository has been archived by the owner on Jan 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Refactor
cognitoData
to dedicated user
/session
objects
This splits the functionality/methods up a bit more. BREAKING CHANGE: Moves method definitions/locations around a bit.
- Loading branch information
Showing
25 changed files
with
1,101 additions
and
711 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Migration from v3.x to v4.x | ||
|
||
When upgrading from v3 to v4, there are a few things to consider. | ||
|
||
## Changed method locations: | ||
|
||
- `cognito.restoreAndLoad()` remains the same; | ||
- `cognito.authenticate()` remains the same; | ||
- `cognito.authenticateUser()` --> `cognito.unauthenticated.verifyUserAuthentication()` | ||
- `cognito.logout()` --> remains the same | ||
- `cognito.invalidateAccessTokens()` --> remains the same | ||
- `cognito.triggerResetPasswordMail()` --> `cognito.unauthenticated.triggerResetPasswordMail()` | ||
- `cognito.updateResetPassword()` --> `cognito.unauthenticated.updateResetPassword()` | ||
- `cognito.setNewPassword()` --> `cognito.unauthenticated.setInitialPassword()` | ||
- `cognito.updatePassword()` --> `cognito.user.updatePassword()` | ||
- `cognito.updateAttributes()` --> `cognito.user.updateAttributes()` | ||
- `cognito.cognitoData.mfa` --> `cognito.user.mfa` | ||
- `cognito.cognitoData.cognitoUser` --> `cognito.user.cognitoUser` | ||
- `cognito.cognitoData.cognitoUserSession` --> `cognito.session.cognitoUserSession` | ||
- `cognito.cognitoData.jwtToken` --> `cognito.session.jwtToken` | ||
- `cognito.cognitoData.userAttributes` --> `cognito.user.userAttributes` | ||
- `cognito.cognitoData.getAccessToken()` --> `cognito.session.getAccessToken()` | ||
- `cognito.cognitoData.getIdToken()` --> `cognito.session.getIdToken()` | ||
- `cognito.refreshAccessToken()` --> `cognito.session.refresh()` | ||
|
||
## `cognitoData` is no more | ||
|
||
As you can see in the above section, `cognito.cognitoData` has been replaced with `cognito.user` and `cognito.session`. | ||
|
||
These two properties will be set when the user is authenticated, else they will be `undefined`. When `isAuthenticated === true` you can assume they are set. | ||
|
||
In contrast, `unauthenticated` is _always_ available. | ||
|
||
## Change token auto-refresh | ||
|
||
In 4.x, JWT tokens will _not_ be automatically refreshed when they expire. | ||
Instead, you can call `cognito.session.enableAutoRefresh()` and `cognito.session.disableAutoRefresh()` to start/stop the auto-refresh background job. | ||
|
||
There are also some new/changed methods to work with token refreshing: | ||
|
||
```js | ||
cognito.session.refresh(); | ||
cognito.session.refreshIfNeeded(); | ||
cognito.session.secondsUntilExpires(); | ||
cognito.session.needsRefresh(); | ||
cognito.session.needsRefreshSoon(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,12 +5,14 @@ Interact with AWS Cognito from your Ember app. | |
This uses `amazon-cognito-identity-js` under the hood, which is considerably smaller in footprint than the quite enormous AWS Amplify SDK. | ||
If all you need is a way to work with the JWT tokens priovded by Cognito, then this addon is perfect for you. | ||
|
||
[Upgrading from 3.x to 4.x](MIGRATION_v4.md) | ||
|
||
## Compatibility | ||
|
||
* Ember.js v3.24 or above | ||
* Ember CLI v3.24 or above | ||
* Node.js v14 or above | ||
* Native promise support required | ||
- Ember.js v3.24 or above | ||
- Ember CLI v3.24 or above | ||
- Node.js v14 or above | ||
- Native promise support required | ||
|
||
## Installation | ||
|
||
|
@@ -59,66 +61,60 @@ export default class ApplicationRoute extends Route { | |
After logging in (see below) you can access the JTW token like this: | ||
|
||
```js | ||
let token = this.cognito.cognitoData.jwtToken; | ||
let token = this.cognito.session.jwtToken; | ||
``` | ||
|
||
Here is a summary of the most important available methods - all methods return a promise: | ||
|
||
```js | ||
cognito.restoreAndLoad(); | ||
cognito.authenticate({ username, password }); | ||
cognito.authenticateUser({ username, password }); | ||
cognito.mfaCompleteAuthentication(code); | ||
cognito.logout(); | ||
cognito.invalidateAccessTokens(); | ||
cognito.triggerResetPasswordMail({ username }); | ||
cognito.updateResetPassword({ username, code, newPassword }); | ||
cognito.setNewPassword({ username, password, newPassword }); | ||
cognito.updatePassword({ oldPassword, newPassword }); | ||
cognito.updateAttributes(attributeMap); | ||
cognito.invalidateAccessToken(); | ||
|
||
cognito.user.updatePassword(); | ||
cognito.user.updateAttributes(); | ||
|
||
cognito.session.refresh(); | ||
cognito.session.refreshIfNeeded(); | ||
cognito.session.secondsUntilExpires(); | ||
|
||
cognito.unauthenticated.verifyUserAuthentication({ username, password }); | ||
cognito.unauthenticated.triggerResetPasswordMail({ username }); | ||
cognito.unauthenticated.updateResetPassword({ username, code, newPassword }); | ||
cognito.unauthenticated.setInitialPassword({ username, password, newPassword }); | ||
``` | ||
|
||
## Cognito service | ||
|
||
The `cognito` service provides promise-ified methods to work with AWS Cognito. | ||
|
||
### isAuthenticated | ||
|
||
Will be true if a user is currently logged in. | ||
This means that you can safely access `cognitoData` and work with it. | ||
```js | ||
if (cognito.isAuthenticated) { | ||
let { user, session } = cognito; | ||
|
||
### cognitoData | ||
// See below for available properties & methods on user/session objects | ||
} else { | ||
let { unauthenticated } = cognito; | ||
|
||
This property will contain an object with your main Cognito-related information, if the user is logged in. | ||
If the user is not logged in, this will be `null`. | ||
// See below for available properties on unauthenticated object | ||
} | ||
``` | ||
|
||
This is an object that looks like this: | ||
### `isAuthenticated` | ||
|
||
```js | ||
let cognitoData = { | ||
cognitoUser: CognitoUser, | ||
cognitoUserSession: CognitoUserSession, | ||
jwtToken: 'xxxxx', | ||
userAttributes: { Email: '...' }, | ||
getAccessToken: () => CognitoAccessToken, | ||
getIdToken: () => CognitoIdToken, | ||
mfa: { | ||
enable: () => {}, | ||
disable: () => {}, | ||
isEnabled: () => {}, | ||
setupDevice: () => {}, | ||
verifyDevice: (code) => {}, | ||
}, | ||
}; | ||
``` | ||
Will be true if a user is currently logged in. | ||
This means that you can safely access `session` and `user` and work with it. | ||
|
||
### restoreAndLoad() | ||
### `restoreAndLoad()` | ||
|
||
Will try to lookup a prior session in local storage and authenticate the user. | ||
|
||
If this resolves, you can assume that the user is logged in. It will reject if the user is not logged in. | ||
Call this (and wait for it to complete) in your application route! | ||
|
||
### authenticate({ username, password }) | ||
### `authenticate({ username, password })` | ||
|
||
Try to login with the given username & password. | ||
Will reject with an Error, or resolve if successfull. | ||
|
@@ -133,53 +129,149 @@ cognito: { | |
} | ||
``` | ||
|
||
### authenticateUser({ username, password }) | ||
### `mfaCompleteAuthentication(code)` | ||
|
||
Verify only the given username & password. | ||
This will _not_ sign the user in, but can be used to e.g. guard special places in your app behind a repeated password check. | ||
Will reject with an Error, or resolve if successfull. | ||
This has to be called when `authenticate()` rejects with `MfaCodeRequiredError`. | ||
|
||
### logout() | ||
Returns a promise & signs the user in (if successful). | ||
|
||
### `logout()` | ||
|
||
Log out the user from the current device. | ||
|
||
### invalidateAccessTokens() | ||
### `invalidateAccessToken()` | ||
|
||
Logout & invalidate all issues access tokens (also on other devices). | ||
Logout & invalidate all issued access tokens (also on other devices). | ||
In contrast, `logout()` does not revoke access tokens, it only removes them locally. | ||
|
||
Returns a promise. | ||
|
||
### triggerResetPasswordMail({ username }) | ||
## session | ||
|
||
Trigger an email to get a verification code to reset the password. | ||
The `session` object is available when the user is signed in. | ||
|
||
Returns a promise. | ||
Example usage: | ||
|
||
```js | ||
cognito.session.jwtToken; | ||
``` | ||
|
||
### updateResetPassword({ username, code, newPassword }) | ||
It provides the following functionality: | ||
|
||
Set a new password for a user. | ||
### `jwtToken` | ||
|
||
The current JWT access token. | ||
This is a tracked property and may be updated in the background. | ||
|
||
### `getAccessToken()` | ||
|
||
Get the AWS access token object. | ||
This includes metadata. | ||
|
||
### `getIdToken()` | ||
|
||
Get the AWS id token object. | ||
This includes metadata. | ||
|
||
### `needsRefresh()` | ||
|
||
Returns true if the session is expired. | ||
|
||
### `needsRefreshSoon()` | ||
|
||
Returns true if the session will expire soon (in the next 15 minutes). | ||
|
||
### `secondsUntilExpires()` | ||
|
||
Returns the number of seconds until the session will expire. | ||
|
||
### `refresh()` | ||
|
||
Refresh the session, generating new JWT tokens. | ||
|
||
This will debounce when being run multiple times simultaneously. | ||
|
||
Returns a promise. | ||
|
||
### setNewPassword({ username, password, newPassword }) | ||
### `refreshIfNeeded()` | ||
|
||
Set a new password, if a user requires a new password to be set (e.g. after an admin created the user). | ||
Refresh the session only if it will expire soon. | ||
|
||
This will debounce when being run multiple times simultaneously. | ||
|
||
Returns a promise. | ||
|
||
### updatePassword({ oldPassword, newPassword }) | ||
## user | ||
|
||
The `user` object is available when the user is signed in. | ||
|
||
Example usage: | ||
|
||
```js | ||
cognito.user.updatePassword({ oldPassword, newPassword }); | ||
``` | ||
|
||
It provides the following functionality: | ||
|
||
### `userAttributes` | ||
|
||
This tracked property provides access to a key-value pair object of user attributes. | ||
|
||
### `updatePassword({ oldPassword, newPassword })` | ||
|
||
Update the password of the currently logged in user. | ||
|
||
Returns a promise. | ||
|
||
## Token expiration | ||
### `updateAttributes(attributes)` | ||
|
||
Update the attributes of the currently logged in user. | ||
|
||
This expects an object with key-value pairs, e.g.: | ||
|
||
```js | ||
user.updateAttributes({ Email: '[email protected]', Name: 'John Doe' }); | ||
``` | ||
|
||
Returns a promise. | ||
|
||
### unauthenticated | ||
|
||
The `unauthenticated` object is always available. | ||
|
||
Example usage: | ||
|
||
```js | ||
cognito.unauthenticated.triggerResetPasswordMail({ username }); | ||
``` | ||
|
||
It provides the following functionality: | ||
|
||
### `verifyUserAuthentication({ username, password })` | ||
|
||
Verify only the given username & password. | ||
This will _not_ sign the user in, but can be used to e.g. guard special places in your app behind a repeated password check. | ||
Will reject with an Error, or resolve if successfull. | ||
|
||
### `triggerResetPasswordMail({ username })` | ||
|
||
Trigger an email to get a verification code to reset the password. | ||
|
||
Returns a promise. | ||
|
||
### `updateResetPassword({ username, code, newPassword })` | ||
|
||
Set a new password for a user. | ||
|
||
This addon will automatically refresh the JWT Token every 15 minutes before it expires. | ||
The tokens have a lifespan of 60 minutes, so this should ensure that the local token never experies in the middle of a session. | ||
Returns a promise. | ||
|
||
### `setInitialPassword({ username, password, newPassword })` | ||
|
||
Set a new password, if a user requires a new password to be set (e.g. after an admin created the user). | ||
|
||
Returns a promise. | ||
|
||
## Multi-Factor Authentication (MFA) | ||
## mfa (Multi-Factor Authentication) | ||
|
||
This addon allows you to work with optional TOTP (Temporary One Time Password) MFA. | ||
SMS-based MFA is not supported for now (to reduce complexity, and since it is less secure than TOTP). | ||
|
@@ -190,10 +282,10 @@ Using MFA in your app requires changes in two places: The sign in process, and a | |
|
||
### Setting up MFA | ||
|
||
A user can opt into MFA for their own account. For this, you can use the `mfa` object on the `cognitoData` object: | ||
A user can opt into MFA for their own account. For this, you can use the `mfa` object on the `user` object: | ||
|
||
```js | ||
let { mfa } = this.cognito.cognitoData; | ||
let { mfa } = this.cognito.user; | ||
|
||
// Available methods | ||
await mfa.enable(); | ||
|
@@ -277,6 +369,16 @@ await this.cognito.mfaCompleteAuthentication(mfaCode); | |
|
||
After that, the user will be signed in (or it will throw an error if the MFA code is incorrect). | ||
|
||
## Token expiration | ||
|
||
The generated JWT tokens have a lifespan of 60 minutes. After that, you need to refresh the session. | ||
|
||
You can call `cognito.session.enableAutoRefresh()` to start an automated background job, | ||
which will refresh the token every 45 minutes. | ||
|
||
Alternatively, you can also manually handle this with the provided `session.needsRefresh()`, | ||
`session.needsRefreshSoon()`, `session.refresh()` and `session.refreshIfNeeded()` methods. | ||
|
||
## Example | ||
|
||
You can find example components in the dummy app to see how a concrete implementation could look like. | ||
|
@@ -371,7 +473,7 @@ test('it works with new password required', function (assert) { | |
|
||
### Generating mocked data | ||
|
||
You can generate mocked cognitoData with the provided utils: | ||
You can generate a mocked user/session with the provided utils: | ||
|
||
```js | ||
import ApplicationInstance from '@ember/application/instance'; | ||
|
@@ -383,7 +485,7 @@ export function initialize(appInstance: ApplicationInstance): void { | |
let cognitoData = mockCognitoData(); | ||
if (cognitoData) { | ||
let cognito = appInstance.lookup('service:cognito'); | ||
cognito.cognitoData = cognitoData; | ||
cognito.setupSession(cognitoData); | ||
} | ||
} | ||
|
||
|
@@ -453,7 +555,7 @@ test('test helper correctly mocks a cognito session', async function (assert) { | |
assert, | ||
}); | ||
|
||
cognito.cognitoData = cognitoData; | ||
cognito.setupSession(cognitoData); | ||
}); | ||
``` | ||
|
||
|
Oops, something went wrong.