Skip to content
This repository was archived by the owner on Jan 24, 2024. It is now read-only.

Commit 68b1bdb

Browse files
committed
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.
1 parent 06af5b7 commit 68b1bdb

25 files changed

+1101
-711
lines changed

MIGRATION_v4.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Migration from v3.x to v4.x
2+
3+
When upgrading from v3 to v4, there are a few things to consider.
4+
5+
## Changed method locations:
6+
7+
- `cognito.restoreAndLoad()` remains the same;
8+
- `cognito.authenticate()` remains the same;
9+
- `cognito.authenticateUser()` --> `cognito.unauthenticated.verifyUserAuthentication()`
10+
- `cognito.logout()` --> remains the same
11+
- `cognito.invalidateAccessTokens()` --> remains the same
12+
- `cognito.triggerResetPasswordMail()` --> `cognito.unauthenticated.triggerResetPasswordMail()`
13+
- `cognito.updateResetPassword()` --> `cognito.unauthenticated.updateResetPassword()`
14+
- `cognito.setNewPassword()` --> `cognito.unauthenticated.setInitialPassword()`
15+
- `cognito.updatePassword()` --> `cognito.user.updatePassword()`
16+
- `cognito.updateAttributes()` --> `cognito.user.updateAttributes()`
17+
- `cognito.cognitoData.mfa` --> `cognito.user.mfa`
18+
- `cognito.cognitoData.cognitoUser` --> `cognito.user.cognitoUser`
19+
- `cognito.cognitoData.cognitoUserSession` --> `cognito.session.cognitoUserSession`
20+
- `cognito.cognitoData.jwtToken` --> `cognito.session.jwtToken`
21+
- `cognito.cognitoData.userAttributes` --> `cognito.user.userAttributes`
22+
- `cognito.cognitoData.getAccessToken()` --> `cognito.session.getAccessToken()`
23+
- `cognito.cognitoData.getIdToken()` --> `cognito.session.getIdToken()`
24+
- `cognito.refreshAccessToken()` --> `cognito.session.refresh()`
25+
26+
## `cognitoData` is no more
27+
28+
As you can see in the above section, `cognito.cognitoData` has been replaced with `cognito.user` and `cognito.session`.
29+
30+
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.
31+
32+
In contrast, `unauthenticated` is _always_ available.
33+
34+
## Change token auto-refresh
35+
36+
In 4.x, JWT tokens will _not_ be automatically refreshed when they expire.
37+
Instead, you can call `cognito.session.enableAutoRefresh()` and `cognito.session.disableAutoRefresh()` to start/stop the auto-refresh background job.
38+
39+
There are also some new/changed methods to work with token refreshing:
40+
41+
```js
42+
cognito.session.refresh();
43+
cognito.session.refreshIfNeeded();
44+
cognito.session.secondsUntilExpires();
45+
cognito.session.needsRefresh();
46+
cognito.session.needsRefreshSoon();
47+
```

README.md

+165-63
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ Interact with AWS Cognito from your Ember app.
55
This uses `amazon-cognito-identity-js` under the hood, which is considerably smaller in footprint than the quite enormous AWS Amplify SDK.
66
If all you need is a way to work with the JWT tokens priovded by Cognito, then this addon is perfect for you.
77

8+
[Upgrading from 3.x to 4.x](MIGRATION_v4.md)
9+
810
## Compatibility
911

10-
* Ember.js v3.24 or above
11-
* Ember CLI v3.24 or above
12-
* Node.js v14 or above
13-
* Native promise support required
12+
- Ember.js v3.24 or above
13+
- Ember CLI v3.24 or above
14+
- Node.js v14 or above
15+
- Native promise support required
1416

1517
## Installation
1618

@@ -59,66 +61,60 @@ export default class ApplicationRoute extends Route {
5961
After logging in (see below) you can access the JTW token like this:
6062

6163
```js
62-
let token = this.cognito.cognitoData.jwtToken;
64+
let token = this.cognito.session.jwtToken;
6365
```
6466

6567
Here is a summary of the most important available methods - all methods return a promise:
6668

6769
```js
6870
cognito.restoreAndLoad();
6971
cognito.authenticate({ username, password });
70-
cognito.authenticateUser({ username, password });
72+
cognito.mfaCompleteAuthentication(code);
7173
cognito.logout();
72-
cognito.invalidateAccessTokens();
73-
cognito.triggerResetPasswordMail({ username });
74-
cognito.updateResetPassword({ username, code, newPassword });
75-
cognito.setNewPassword({ username, password, newPassword });
76-
cognito.updatePassword({ oldPassword, newPassword });
77-
cognito.updateAttributes(attributeMap);
74+
cognito.invalidateAccessToken();
75+
76+
cognito.user.updatePassword();
77+
cognito.user.updateAttributes();
78+
79+
cognito.session.refresh();
80+
cognito.session.refreshIfNeeded();
81+
cognito.session.secondsUntilExpires();
82+
83+
cognito.unauthenticated.verifyUserAuthentication({ username, password });
84+
cognito.unauthenticated.triggerResetPasswordMail({ username });
85+
cognito.unauthenticated.updateResetPassword({ username, code, newPassword });
86+
cognito.unauthenticated.setInitialPassword({ username, password, newPassword });
7887
```
7988

8089
## Cognito service
8190

8291
The `cognito` service provides promise-ified methods to work with AWS Cognito.
8392

84-
### isAuthenticated
85-
86-
Will be true if a user is currently logged in.
87-
This means that you can safely access `cognitoData` and work with it.
93+
```js
94+
if (cognito.isAuthenticated) {
95+
let { user, session } = cognito;
8896

89-
### cognitoData
97+
// See below for available properties & methods on user/session objects
98+
} else {
99+
let { unauthenticated } = cognito;
90100

91-
This property will contain an object with your main Cognito-related information, if the user is logged in.
92-
If the user is not logged in, this will be `null`.
101+
// See below for available properties on unauthenticated object
102+
}
103+
```
93104

94-
This is an object that looks like this:
105+
### `isAuthenticated`
95106

96-
```js
97-
let cognitoData = {
98-
cognitoUser: CognitoUser,
99-
cognitoUserSession: CognitoUserSession,
100-
jwtToken: 'xxxxx',
101-
userAttributes: { Email: '...' },
102-
getAccessToken: () => CognitoAccessToken,
103-
getIdToken: () => CognitoIdToken,
104-
mfa: {
105-
enable: () => {},
106-
disable: () => {},
107-
isEnabled: () => {},
108-
setupDevice: () => {},
109-
verifyDevice: (code) => {},
110-
},
111-
};
112-
```
107+
Will be true if a user is currently logged in.
108+
This means that you can safely access `session` and `user` and work with it.
113109

114-
### restoreAndLoad()
110+
### `restoreAndLoad()`
115111

116112
Will try to lookup a prior session in local storage and authenticate the user.
117113

118114
If this resolves, you can assume that the user is logged in. It will reject if the user is not logged in.
119115
Call this (and wait for it to complete) in your application route!
120116

121-
### authenticate({ username, password })
117+
### `authenticate({ username, password })`
122118

123119
Try to login with the given username & password.
124120
Will reject with an Error, or resolve if successfull.
@@ -133,53 +129,149 @@ cognito: {
133129
}
134130
```
135131

136-
### authenticateUser({ username, password })
132+
### `mfaCompleteAuthentication(code)`
137133

138-
Verify only the given username & password.
139-
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.
140-
Will reject with an Error, or resolve if successfull.
134+
This has to be called when `authenticate()` rejects with `MfaCodeRequiredError`.
141135

142-
### logout()
136+
Returns a promise & signs the user in (if successful).
137+
138+
### `logout()`
143139

144140
Log out the user from the current device.
145141

146-
### invalidateAccessTokens()
142+
### `invalidateAccessToken()`
147143

148-
Logout & invalidate all issues access tokens (also on other devices).
144+
Logout & invalidate all issued access tokens (also on other devices).
149145
In contrast, `logout()` does not revoke access tokens, it only removes them locally.
150146

151147
Returns a promise.
152148

153-
### triggerResetPasswordMail({ username })
149+
## session
154150

155-
Trigger an email to get a verification code to reset the password.
151+
The `session` object is available when the user is signed in.
156152

157-
Returns a promise.
153+
Example usage:
154+
155+
```js
156+
cognito.session.jwtToken;
157+
```
158158

159-
### updateResetPassword({ username, code, newPassword })
159+
It provides the following functionality:
160160

161-
Set a new password for a user.
161+
### `jwtToken`
162+
163+
The current JWT access token.
164+
This is a tracked property and may be updated in the background.
165+
166+
### `getAccessToken()`
167+
168+
Get the AWS access token object.
169+
This includes metadata.
170+
171+
### `getIdToken()`
172+
173+
Get the AWS id token object.
174+
This includes metadata.
175+
176+
### `needsRefresh()`
177+
178+
Returns true if the session is expired.
179+
180+
### `needsRefreshSoon()`
181+
182+
Returns true if the session will expire soon (in the next 15 minutes).
183+
184+
### `secondsUntilExpires()`
185+
186+
Returns the number of seconds until the session will expire.
187+
188+
### `refresh()`
189+
190+
Refresh the session, generating new JWT tokens.
191+
192+
This will debounce when being run multiple times simultaneously.
162193

163194
Returns a promise.
164195

165-
### setNewPassword({ username, password, newPassword })
196+
### `refreshIfNeeded()`
166197

167-
Set a new password, if a user requires a new password to be set (e.g. after an admin created the user).
198+
Refresh the session only if it will expire soon.
199+
200+
This will debounce when being run multiple times simultaneously.
168201

169202
Returns a promise.
170203

171-
### updatePassword({ oldPassword, newPassword })
204+
## user
205+
206+
The `user` object is available when the user is signed in.
207+
208+
Example usage:
209+
210+
```js
211+
cognito.user.updatePassword({ oldPassword, newPassword });
212+
```
213+
214+
It provides the following functionality:
215+
216+
### `userAttributes`
217+
218+
This tracked property provides access to a key-value pair object of user attributes.
219+
220+
### `updatePassword({ oldPassword, newPassword })`
172221

173222
Update the password of the currently logged in user.
174223

175224
Returns a promise.
176225

177-
## Token expiration
226+
### `updateAttributes(attributes)`
227+
228+
Update the attributes of the currently logged in user.
229+
230+
This expects an object with key-value pairs, e.g.:
231+
232+
```js
233+
user.updateAttributes({ Email: 'my@email.com', Name: 'John Doe' });
234+
```
235+
236+
Returns a promise.
237+
238+
### unauthenticated
239+
240+
The `unauthenticated` object is always available.
241+
242+
Example usage:
243+
244+
```js
245+
cognito.unauthenticated.triggerResetPasswordMail({ username });
246+
```
247+
248+
It provides the following functionality:
249+
250+
### `verifyUserAuthentication({ username, password })`
251+
252+
Verify only the given username & password.
253+
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.
254+
Will reject with an Error, or resolve if successfull.
255+
256+
### `triggerResetPasswordMail({ username })`
257+
258+
Trigger an email to get a verification code to reset the password.
259+
260+
Returns a promise.
261+
262+
### `updateResetPassword({ username, code, newPassword })`
263+
264+
Set a new password for a user.
178265

179-
This addon will automatically refresh the JWT Token every 15 minutes before it expires.
180-
The tokens have a lifespan of 60 minutes, so this should ensure that the local token never experies in the middle of a session.
266+
Returns a promise.
267+
268+
### `setInitialPassword({ username, password, newPassword })`
269+
270+
Set a new password, if a user requires a new password to be set (e.g. after an admin created the user).
271+
272+
Returns a promise.
181273

182-
## Multi-Factor Authentication (MFA)
274+
## mfa (Multi-Factor Authentication)
183275

184276
This addon allows you to work with optional TOTP (Temporary One Time Password) MFA.
185277
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
190282

191283
### Setting up MFA
192284

193-
A user can opt into MFA for their own account. For this, you can use the `mfa` object on the `cognitoData` object:
285+
A user can opt into MFA for their own account. For this, you can use the `mfa` object on the `user` object:
194286

195287
```js
196-
let { mfa } = this.cognito.cognitoData;
288+
let { mfa } = this.cognito.user;
197289

198290
// Available methods
199291
await mfa.enable();
@@ -277,6 +369,16 @@ await this.cognito.mfaCompleteAuthentication(mfaCode);
277369

278370
After that, the user will be signed in (or it will throw an error if the MFA code is incorrect).
279371

372+
## Token expiration
373+
374+
The generated JWT tokens have a lifespan of 60 minutes. After that, you need to refresh the session.
375+
376+
You can call `cognito.session.enableAutoRefresh()` to start an automated background job,
377+
which will refresh the token every 45 minutes.
378+
379+
Alternatively, you can also manually handle this with the provided `session.needsRefresh()`,
380+
`session.needsRefreshSoon()`, `session.refresh()` and `session.refreshIfNeeded()` methods.
381+
280382
## Example
281383

282384
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) {
371473

372474
### Generating mocked data
373475

374-
You can generate mocked cognitoData with the provided utils:
476+
You can generate a mocked user/session with the provided utils:
375477

376478
```js
377479
import ApplicationInstance from '@ember/application/instance';
@@ -383,7 +485,7 @@ export function initialize(appInstance: ApplicationInstance): void {
383485
let cognitoData = mockCognitoData();
384486
if (cognitoData) {
385487
let cognito = appInstance.lookup('service:cognito');
386-
cognito.cognitoData = cognitoData;
488+
cognito.setupSession(cognitoData);
387489
}
388490
}
389491

@@ -453,7 +555,7 @@ test('test helper correctly mocks a cognito session', async function (assert) {
453555
assert,
454556
});
455557

456-
cognito.cognitoData = cognitoData;
558+
cognito.setupSession(cognitoData);
457559
});
458560
```
459561

0 commit comments

Comments
 (0)