Skip to content

Commit 592e0ce

Browse files
authored
Fixed stored credentials reset issues. Added check for stored token expiration. (#943)
1 parent dafb110 commit 592e0ce

File tree

9 files changed

+104
-71
lines changed

9 files changed

+104
-71
lines changed

src/apim.runtime.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import { BalloonBindingHandler, ResizableBindingHandler } from "@paperbits/core/
6868
import { TagInput } from "./components/tag-input/tag-input";
6969
import { ViewStack } from "@paperbits/core/ko/ui/viewStack";
7070
import { OAuthService } from "./services/oauthService";
71-
import { DefaultSessionManager } from "./authentication/sessionManager";
71+
import { DefaultSessionManager } from "./authentication/defaultSessionManager";
7272

7373
export class ApimRuntimeModule implements IInjectorModule {
7474
public register(injector: IInjector): void {

src/authentication/accessToken.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ export class AccessToken {
5454

5555
private static parseBearerToken(value: string): AccessToken {
5656
const decodedToken = Utils.parseJwt(value);
57-
const exp = moment(decodedToken.exp).toDate();
58-
59-
return new AccessToken("Bearer", value, exp);
57+
return new AccessToken("Bearer", value, decodedToken.exp);
6058
}
6159

6260
public static parse(token: string): AccessToken {
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
export interface SessionManager {
2-
/**
3-
* Returns stored value.
4-
* @param key
5-
*/
6-
getItem<T>(key: string): Promise<T>;
7-
8-
/**
9-
* Stores value with specified key.
10-
* @param key {string} Stored value key.
11-
* @param value {T} Stored value.
12-
*/
13-
setItem<T>(key: string, value: T): Promise<void>;
1+
import { SessionManager } from "./sessionManager";
2+
3+
export class DefaultSessionManager implements SessionManager {
4+
public async getItem<T>(key: string): Promise<T> {
5+
const value = sessionStorage.getItem(key);
6+
7+
if (!value) {
8+
return null;
9+
}
10+
11+
return JSON.parse(value);
12+
}
13+
14+
public async setItem<T>(key: string, value: T): Promise<void> {
15+
sessionStorage.setItem(key, JSON.stringify(value));
16+
}
17+
18+
public async removeItem(key: string): Promise<void> {
19+
sessionStorage.removeItem(key);
20+
}
1421
}

src/authentication/sessionManager.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
export class DefaultSessionManager {
2-
public async getItem<T>(key: string): Promise<T> {
3-
const value = sessionStorage.getItem(key);
4-
return JSON.parse(value);
5-
}
1+
export interface SessionManager {
2+
/**
3+
* Returns stored value.
4+
* @param key
5+
*/
6+
getItem<T>(key: string): Promise<T>;
67

7-
public async setItem<T>(key: string, value: T): Promise<void> {
8-
sessionStorage.setItem(key, JSON.stringify(value));
9-
}
8+
/**
9+
* Stores value with specified key.
10+
* @param key {string} Stored value key.
11+
* @param value {T} Stored value.
12+
*/
13+
setItem<T>(key: string, value: T): Promise<void>;
14+
15+
/**
16+
* Removes value with specified key.
17+
* @param key {string} Stored value key.
18+
*/
19+
removeItem(key: string): Promise<void>;
1020
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
export interface StoredCredentials {
2+
grantType: string;
3+
accessToken: string;
4+
}
5+
16
export interface OAuthSession {
2-
[apiName: string]: {
3-
grantType: string;
4-
accessToken: string;
5-
};
7+
[apiName: string]: StoredCredentials;
68
}

src/components/operations/operation-details/ko/runtime/operation-console.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import { RouteHelper } from "../../../../../routing/routeHelper";
2525
import { TemplatingService } from "../../../../../services/templatingService";
2626
import { OAuthService } from "../../../../../services/oauthService";
2727
import { AuthorizationServer } from "../../../../../models/authorizationServer";
28-
import { SessionManager } from "./../../../../../authentication/defaultSessionManager";
29-
import { OAuthSession } from "./oauthSession";
28+
import { SessionManager } from "../../../../../authentication/sessionManager";
29+
import { OAuthSession, StoredCredentials } from "./oauthSession";
3030

3131
const oauthSessionKey = "oauthSession";
3232

@@ -527,23 +527,34 @@ export class OperationConsole {
527527

528528
const api = this.api();
529529
const scopeOverride = api.authenticationSettings?.oAuth2?.scope;
530-
const recordKey = this.getSessionRecordKey(authorizationServer.name, scopeOverride);
531-
const oauthSession = await this.sessionManager.getItem<OAuthSession>(oauthSessionKey);
532-
const record = oauthSession?.[recordKey];
530+
const storedCredentials = await this.getStoredCredentials(authorizationServer.name, scopeOverride);
533531

534-
if (record) {
535-
this.selectedGrantType(record.grantType);
536-
this.setAuthorizationHeader(record.accessToken);
532+
if (storedCredentials) {
533+
this.selectedGrantType(storedCredentials.grantType);
534+
this.setAuthorizationHeader(storedCredentials.accessToken);
537535
}
538536
}
539537

540-
private async getStoredCredentials(serverName: string, scopeOverride: string): Promise<string> {
538+
private async getStoredCredentials(serverName: string, scopeOverride: string): Promise<StoredCredentials> {
541539
const oauthSession = await this.sessionManager.getItem<OAuthSession>(oauthSessionKey);
542540
const recordKey = this.getSessionRecordKey(serverName, scopeOverride);
543-
const record = oauthSession?.[recordKey];
544-
const accessToken = record?.accessToken;
541+
const storedCredentials = oauthSession?.[recordKey];
542+
543+
try {
544+
/* Trying to check if it's a JWT token and, if yes, whether it got expired. */
545+
const jwtToken = Utils.parseJwt(storedCredentials.accessToken.replace(/^bearer /i, ""));
546+
const now = Utils.getUtcDateTime();
547+
548+
if (now > jwtToken.exp) {
549+
await this.clearStoredCredentials();
550+
return null;
551+
}
552+
}
553+
catch (error) {
554+
// do nothing
555+
}
545556

546-
return accessToken;
557+
return storedCredentials;
547558
}
548559

549560
private async setStoredCredentials(serverName: string, scopeOverride: string, grantType: string, accessToken: string): Promise<void> {
@@ -558,12 +569,17 @@ export class OperationConsole {
558569
await this.sessionManager.setItem<object>(oauthSessionKey, oauthSession);
559570
}
560571

572+
private async clearStoredCredentials(): Promise<void> {
573+
await this.sessionManager.removeItem(oauthSessionKey);
574+
this.removeAuthorizationHeader();
575+
}
576+
561577
/**
562578
* Initiates specified authentication flow.
563579
* @param grantType OAuth grant type, e.g. "implicit" or "authorizationCode".
564580
*/
565581
public async authenticateOAuth(grantType: string): Promise<void> {
566-
this.removeAuthorizationHeader();
582+
await this.clearStoredCredentials();
567583

568584
if (!grantType) {
569585
return;
@@ -573,10 +589,10 @@ export class OperationConsole {
573589
const authorizationServer = this.authorizationServer();
574590
const scopeOverride = api.authenticationSettings?.oAuth2?.scope;
575591
const serverName = authorizationServer.name;
576-
const storedAccessToken = await this.getStoredCredentials(serverName, scopeOverride);
592+
const storedCredentials = await this.getStoredCredentials(serverName, scopeOverride);
577593

578-
if (storedAccessToken) {
579-
this.setAuthorizationHeader(storedAccessToken);
594+
if (storedCredentials) {
595+
this.setAuthorizationHeader(storedCredentials.accessToken);
580596
return;
581597
}
582598

src/contracts/jwtToken.ts

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ export interface JwtToken {
44
*/
55
acr: string;
66

7-
aio: string;
8-
97
/**
108
* Authentication methods array
119
*/
@@ -16,19 +14,15 @@ export interface JwtToken {
1614
*/
1715
appid: string;
1816

19-
appidacr: string;
20-
2117
/**
2218
* Audience (who or what the token is intended for)
2319
*/
2420
aud: string;
2521

26-
deviceid: string;
27-
2822
/**
29-
* Expiration time (seconds since Unix epoch)
23+
* Expiration time (UTC)
3024
*/
31-
exp: number;
25+
exp: Date;
3226

3327
/**
3428
* Family name of the user
@@ -40,12 +34,10 @@ export interface JwtToken {
4034
*/
4135
given_name: string;
4236

43-
hasgroups: string;
44-
4537
/**
46-
* Issued at (seconds since Unix epoch)
38+
* Issued at (UTC)
4739
*/
48-
iat: number;
40+
iat: Date;
4941

5042
/**
5143
* IP address
@@ -56,30 +48,21 @@ export interface JwtToken {
5648
* Issuer (who created and signed this token)
5749
*/
5850
iss: string;
59-
name: string;
6051

6152
/**
62-
* Not valid before (seconds since Unix epoch)
53+
* Not valid before (UTC).
6354
*/
64-
nbf: number;
55+
nbf: Date;
6556

6657
/**
6758
* Object ID.
6859
*/
6960
oid: string;
70-
onprem_sid: string;
71-
puid: string;
72-
scp: string;
7361

7462
/**
7563
* Subject (whom the token refers to)
7664
*/
7765
sub: string;
78-
tid: string;
79-
unique_name: string;
80-
upn: string;
81-
uti: string;
82-
ver: string;
8366

8467
/**
8568
* Email address.

src/services/oauthService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PageContract } from "../contracts/page";
99
import { OpenIdConnectProviderContract } from "../contracts/openIdConnectProvider";
1010
import { OpenIdConnectProvider } from "./../models/openIdConnectProvider";
1111
import { OpenIdConnectMetadata } from "./../contracts/openIdConnectMetadata";
12-
import { AppError } from "../errors";
12+
1313

1414
export class OAuthService {
1515
constructor(

src/utils.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ArmResource } from "./contracts/armResource";
22
import { NameValuePair } from "request";
33
import { JwtToken } from "./contracts/jwtToken";
44
import { js } from "js-beautify";
5+
import { off } from "process";
56

67
export class Utils {
78
public static getResourceName(resource: string, fullId: string, resultType: string = "name"): string {
@@ -270,8 +271,24 @@ export class Utils {
270271
public static parseJwt(jwtToken: string): JwtToken {
271272
const base64Url = jwtToken.split(".")[1];
272273
const base64 = base64Url.replace("-", "+").replace("_", "/");
274+
const decodedToken = JSON.parse(window.atob(base64));
273275

274-
return JSON.parse(window.atob(base64));
276+
const now = new Date();
277+
const offset = now.getTimezoneOffset() * 60000 * 1000;
278+
279+
if (decodedToken.exp) {
280+
decodedToken.exp = new Date(decodedToken.exp + offset);
281+
}
282+
283+
if (decodedToken.nfb) {
284+
decodedToken.nfb = new Date(decodedToken.nfb + offset);
285+
}
286+
287+
if (decodedToken.iat) {
288+
decodedToken.iat = new Date(decodedToken.iat + offset);
289+
}
290+
291+
return decodedToken;
275292
}
276293

277294
public static scrollTo(id: string): void {
@@ -338,7 +355,7 @@ export class Utils {
338355

339356
return utc;
340357
}
341-
358+
342359
public static readFileAsByteArray(file: File): Promise<Uint8Array> {
343360
return new Promise<Uint8Array>(resolve => {
344361
const reader = new FileReader();

0 commit comments

Comments
 (0)