Skip to content

Commit b8cb93e

Browse files
committed
refactor(typescript): OAuth2PasswordGrantAuthenticator
1 parent 0a28405 commit b8cb93e

File tree

2 files changed

+113
-50
lines changed

2 files changed

+113
-50
lines changed

packages/ember-simple-auth/src/authenticators/base.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
115115
@member
116116
@public
117117
*/
118-
restore() {
118+
restore(...args: any[]): Promise<unknown> {
119119
return Promise.reject();
120120
}
121121

@@ -144,7 +144,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
144144
@member
145145
@public
146146
*/
147-
authenticate() {
147+
authenticate(...args: any[]): Promise<unknown> {
148148
return Promise.reject();
149149
}
150150

@@ -169,7 +169,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
169169
@member
170170
@public
171171
*/
172-
invalidate() {
172+
invalidate(...args: any[]): Promise<unknown> {
173173
return Promise.resolve();
174174
}
175175

@@ -189,7 +189,7 @@ export default class EsaBaseAuthenticator extends EmberObject {
189189

190190
trigger<Event extends keyof AuthenticatorEvents>(
191191
event: Event,
192-
value: AuthenticatorEvents[Event]
192+
value: AuthenticatorEvents[Event]['detail']
193193
) {
194194
let customEvent;
195195
if (value) {

packages/ember-simple-auth/src/authenticators/oauth2-password-grant.js renamed to packages/ember-simple-auth/src/authenticators/oauth2-password-grant.ts

Lines changed: 109 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,50 @@
1-
import { isEmpty } from '@ember/utils';
2-
import { run, later, cancel } from '@ember/runloop';
31
import { A, makeArray } from '@ember/array';
42
import { warn } from '@ember/debug';
53
import { getOwner } from '@ember/application';
64
import BaseAuthenticator from './base';
75
import isFastBoot from '../utils/is-fastboot';
86
import { waitFor } from '@ember/test-waiters';
97
import { isTesting } from '@embroider/macros';
8+
import type { Timer } from '@ember/runloop';
9+
import { run } from '@ember/runloop';
10+
import { cancel } from '@ember/runloop';
11+
import { later } from '@ember/runloop';
12+
13+
type OAuthResponseSuccess = {
14+
access_token: string;
15+
token_type: string;
16+
expires_in?: number;
17+
expires_at?: number;
18+
refresh_token?: string;
19+
scope?: string;
20+
};
21+
22+
type OAuthPasswordRequestData = {
23+
grant_type: string;
24+
username: string;
25+
password: string;
26+
client_id?: string;
27+
scope?: string;
28+
};
29+
30+
type OAuthInvalidateRequestData = {
31+
token_type_hint: 'access_token' | 'refresh_token';
32+
token: string;
33+
client_id?: string;
34+
scope?: string;
35+
};
36+
37+
type OAuthRefreshRequestData = {
38+
grant_type: 'refresh_token';
39+
refresh_token: string;
40+
scope?: string;
41+
client_id?: string;
42+
};
43+
44+
type MakeRequestData =
45+
| OAuthPasswordRequestData
46+
| OAuthInvalidateRequestData
47+
| OAuthRefreshRequestData;
1048

1149
/**
1250
Authenticator that conforms to OAuth 2
@@ -46,7 +84,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
4684
@default null
4785
@public
4886
*/
49-
clientId = null;
87+
clientId: string | null = null;
5088

5189
/**
5290
The endpoint on the server that authentication and token refresh requests
@@ -58,7 +96,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
5896
@default '/token'
5997
@public
6098
*/
61-
serverTokenEndpoint = '/token';
99+
serverTokenEndpoint: string = '/token';
62100

63101
/**
64102
The endpoint on the server that token revocation requests are sent to. Only
@@ -75,7 +113,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
75113
@default null
76114
@public
77115
*/
78-
serverTokenRevocationEndpoint = null;
116+
serverTokenRevocationEndpoint: string | null = null;
79117

80118
/**
81119
Sets whether the authenticator automatically refreshes access tokens if the
@@ -124,7 +162,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
124162
return (Math.floor(Math.random() * (max - min)) + min) * 1000;
125163
}
126164

127-
_refreshTokenTimeout = null;
165+
_refreshTokenTimeout: Timer | undefined = undefined;
128166

129167
/**
130168
Restores the session from a session data object; __will return a resolving
@@ -144,16 +182,17 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
144182
@return {Promise} A promise that when it resolves results in the session becoming or remaining authenticated. If restoration fails, the promise will reject with the server response (in case the access token had expired and was refreshed using a refresh token); however, the authenticator reads that response already so if you need to read it again you need to clone the response object first
145183
@public
146184
*/
147-
restore(data) {
185+
restore(data: OAuthResponseSuccess) {
148186
return new Promise((resolve, reject) => {
149187
const now = new Date().getTime();
150188
const refreshAccessTokens = this.get('refreshAccessTokens');
151-
if (!isEmpty(data['expires_at']) && data['expires_at'] < now) {
189+
if (data['expires_at'] && data['expires_at'] < now) {
152190
if (refreshAccessTokens) {
153-
this._refreshAccessToken(data['expires_in'], data['refresh_token'], data['scope']).then(
154-
resolve,
155-
reject
156-
);
191+
this._refreshAccessToken(
192+
data['expires_in'],
193+
data['refresh_token'] as string,
194+
data['scope']
195+
).then(resolve, reject);
157196
} else {
158197
reject();
159198
}
@@ -228,13 +267,17 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
228267
@return {Promise} A promise that when it resolves results in the session becoming authenticated. If authentication fails, the promise will reject with the server response; however, the authenticator reads that response already so if you need to read it again you need to clone the response object first
229268
@public
230269
*/
231-
authenticate(identification, password, scope = [], headers = {}) {
270+
authenticate(identification: string, password: string, scope = [], headers = {}) {
232271
return new Promise((resolve, reject) => {
233-
const data = { grant_type: 'password', username: identification, password };
272+
const data: OAuthPasswordRequestData = {
273+
grant_type: 'password',
274+
username: identification,
275+
password,
276+
};
234277
const serverTokenEndpoint = this.get('serverTokenEndpoint');
235278

236279
const scopesString = makeArray(scope).join(' ');
237-
if (!isEmpty(scopesString)) {
280+
if (scopesString.trim().length > 0) {
238281
data.scope = scopesString;
239282
}
240283
this.makeRequest(serverTokenEndpoint, data, headers).then(
@@ -250,7 +293,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
250293
expiresAt,
251294
response['refresh_token']
252295
);
253-
if (!isEmpty(expiresAt)) {
296+
if (expiresAt) {
254297
response = Object.assign(response, { expires_at: expiresAt });
255298
}
256299

@@ -279,21 +322,21 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
279322
@return {Promise} A promise that when it resolves results in the session being invalidated. If invalidation fails, the promise will reject with the server response (in case token revocation is used); however, the authenticator reads that response already so if you need to read it again you need to clone the response object first
280323
@public
281324
*/
282-
invalidate(data) {
325+
invalidate(data: OAuthResponseSuccess) {
283326
const serverTokenRevocationEndpoint = this.get('serverTokenRevocationEndpoint');
284-
function success(resolve) {
327+
const success = (resolve: (value?: unknown) => void) => {
285328
cancel(this._refreshTokenTimeout);
286329
delete this._refreshTokenTimeout;
287330
resolve();
288-
}
331+
};
289332
return new Promise(resolve => {
290-
if (isEmpty(serverTokenRevocationEndpoint)) {
291-
success.apply(this, [resolve]);
333+
if (!serverTokenRevocationEndpoint) {
334+
success(resolve);
292335
} else {
293-
const requests = [];
294-
A(['access_token', 'refresh_token']).forEach(tokenType => {
336+
const requests: Promise<OAuthResponseSuccess>[] = [];
337+
(['access_token', 'refresh_token'] as const).forEach(tokenType => {
295338
const token = data[tokenType];
296-
if (!isEmpty(token)) {
339+
if (token) {
297340
requests.push(
298341
this.makeRequest(serverTokenRevocationEndpoint, {
299342
token_type_hint: tokenType,
@@ -322,18 +365,29 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
322365
@protected
323366
*/
324367
@waitFor
325-
makeRequest(url, data, headers = {}) {
368+
makeRequest(
369+
url: string,
370+
data: MakeRequestData,
371+
headers: Record<string, string> = {}
372+
): Promise<OAuthResponseSuccess & { responseText: string } & { responseJSON: string }> {
326373
headers['Content-Type'] = 'application/x-www-form-urlencoded';
327374

328375
const clientId = this.get('clientId');
329-
if (!isEmpty(clientId)) {
330-
data['client_id'] = this.get('clientId');
376+
if (clientId) {
377+
data.client_id = clientId;
331378
}
332379

333380
const body = Object.keys(data)
334381
.map(key => {
335-
return `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`;
382+
const value = data[key as keyof MakeRequestData];
383+
384+
if (value) {
385+
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
386+
} else {
387+
return null;
388+
}
336389
})
390+
.filter(Boolean)
337391
.join('&');
338392

339393
const options = {
@@ -349,13 +403,15 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
349403
try {
350404
let json = JSON.parse(text);
351405
if (!response.ok) {
352-
response.responseJSON = json;
406+
// @TODO: migrate the old AJAX API.
407+
(response as any).responseJSON = json;
353408
reject(response);
354409
} else {
355410
resolve(json);
356411
}
357412
} catch (SyntaxError) {
358-
response.responseText = text;
413+
// @TODO: migrate the old AJAX API.
414+
(response as any).responseText = text;
359415
reject(response);
360416
}
361417
});
@@ -364,34 +420,41 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
364420
});
365421
}
366422

367-
_scheduleAccessTokenRefresh(expiresIn, expiresAt, refreshToken) {
423+
_scheduleAccessTokenRefresh(
424+
expiresIn: number | undefined,
425+
expiresAt: number | null | undefined,
426+
refreshToken: string | undefined
427+
) {
368428
const refreshAccessTokens = this.get('refreshAccessTokens') && !isFastBoot(getOwner(this));
369429
if (refreshAccessTokens) {
370430
const now = new Date().getTime();
371-
if (isEmpty(expiresAt) && !isEmpty(expiresIn)) {
431+
if (!expiresAt && expiresIn) {
372432
expiresAt = new Date(now + expiresIn * 1000).getTime();
373433
}
374434
const offset = this.get('tokenRefreshOffset');
375-
if (!isEmpty(refreshToken) && !isEmpty(expiresAt) && expiresAt > now - offset) {
435+
if (refreshToken && expiresAt && expiresAt > now - offset) {
376436
cancel(this._refreshTokenTimeout);
377437
delete this._refreshTokenTimeout;
378438
if (!isTesting()) {
379439
this._refreshTokenTimeout = later(
380-
this,
381-
this._refreshAccessToken,
382-
expiresIn,
383-
refreshToken,
384-
expiresAt - now - offset
440+
() => {
441+
this._refreshAccessToken(expiresIn, refreshToken);
442+
},
443+
(expiresAt as number) - now - offset
385444
);
386445
}
387446
}
388447
}
389448
}
390449

391-
_refreshAccessToken(expiresIn, refreshToken, scope) {
392-
const data = { grant_type: 'refresh_token', refresh_token: refreshToken };
450+
_refreshAccessToken(expiresIn: number | undefined, refreshToken: string, scope?: string) {
451+
const data: OAuthRefreshRequestData = {
452+
grant_type: 'refresh_token',
453+
refresh_token: refreshToken,
454+
scope: '',
455+
};
393456
const refreshAccessTokensWithScope = this.get('refreshAccessTokensWithScope');
394-
if (refreshAccessTokensWithScope && !isEmpty(scope)) {
457+
if (refreshAccessTokensWithScope && scope) {
395458
data.scope = scope;
396459
}
397460

@@ -409,7 +472,7 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
409472
expires_at: expiresAt,
410473
refresh_token: refreshToken,
411474
});
412-
if (refreshAccessTokensWithScope && !isEmpty(scope)) {
475+
if (refreshAccessTokensWithScope && scope) {
413476
data.scope = scope;
414477
}
415478
this._scheduleAccessTokenRefresh(expiresIn, null, refreshToken);
@@ -429,13 +492,13 @@ export default class OAuth2PasswordGrantAuthenticator extends BaseAuthenticator
429492
});
430493
}
431494

432-
_absolutizeExpirationTime(expiresIn) {
433-
if (!isEmpty(expiresIn)) {
495+
_absolutizeExpirationTime(expiresIn: number | undefined) {
496+
if (expiresIn) {
434497
return new Date(new Date().getTime() + expiresIn * 1000).getTime();
435498
}
436499
}
437500

438-
_validate(data) {
439-
return !isEmpty(data['access_token']);
501+
_validate(data: OAuthResponseSuccess) {
502+
return Boolean(data['access_token']);
440503
}
441504
}

0 commit comments

Comments
 (0)