Skip to content

Commit

Permalink
Added support for refresh token in authenticator (#502)
Browse files Browse the repository at this point in the history
* added support for refresh token in authenticator

* added refresh token check after request
  • Loading branch information
ygrik authored Feb 24, 2020
1 parent d8698ea commit 8acab71
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 14 deletions.
7 changes: 7 additions & 0 deletions src/authentication/IAuthenticator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccessToken } from "./accessToken";
import { HttpHeader } from "@paperbits/common/http/httpHeader";

export interface IAuthenticator {
/**
Expand All @@ -12,6 +13,12 @@ export interface IAuthenticator {
*/
setAccessToken(accessToken: string): Promise<void>;

/**
* Sets new token for the session from response header and return refreshed value
* @param responseHeaders {HttpHeader[]} Response headers.
*/
refreshAccessTokenFromHeader(responseHeaders: HttpHeader[]): Promise<string>;

/**
* Parses specified access token.
* @param accessToken
Expand Down
32 changes: 31 additions & 1 deletion src/components/defaultAuthenticator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as moment from "moment";
import { Utils } from "../utils";
import { IAuthenticator, AccessToken } from "./../authentication";
import { HttpHeader } from "@paperbits/common/http/httpHeader";

export class DefaultAuthenticator implements IAuthenticator {
public async getAccessToken(): Promise<string> {
Expand All @@ -11,6 +12,26 @@ export class DefaultAuthenticator implements IAuthenticator {
sessionStorage.setItem("accessToken", accessToken);
}

public async refreshAccessTokenFromHeader(responseHeaders: HttpHeader[] = []): Promise<string> {
const accessTokenHeader = responseHeaders.find(x => x.name.toLowerCase() === "ocp-apim-sas-token");
if (accessTokenHeader && accessTokenHeader.value) {
const regex = /token=\"(.*)",refresh/gm;
const match = regex.exec(accessTokenHeader.value);

if (!match || match.length < 2) {
console.error(`Token format is not valid.`);
}

const accessToken = `SharedAccessSignature ${accessTokenHeader.value}`;
const current = sessionStorage.getItem("accessToken");
if (current !== accessToken) {
sessionStorage.setItem("accessToken", accessToken);
return accessToken;
}
}
return undefined;
}

public async clearAccessToken(): Promise<void> {
sessionStorage.removeItem("accessToken");
}
Expand All @@ -33,7 +54,16 @@ export class DefaultAuthenticator implements IAuthenticator {
return now < parsedToken.expires;
}

private parseSharedAccessSignature(accessToken: string): AccessToken {
private parseSharedAccessSignature(fullAccessToken: string): AccessToken {
let accessToken = fullAccessToken;
const refreshRegex = /token=\"(.*)",refresh/gm;
const refreshMatch = refreshRegex.exec(fullAccessToken);
if (!refreshMatch || refreshMatch.length < 2) {
console.error(`Token is not full.`);
} else {
accessToken = refreshMatch[1];
}

const regex = /^[\w\-]*\&(\d*)\&/gm;
const match = regex.exec(accessToken);

Expand Down
21 changes: 21 additions & 0 deletions src/components/staticAuthenticator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IAuthenticator } from "../authentication";
import { HttpHeader } from "@paperbits/common/http/httpHeader";

export class StaticAuthenticator implements IAuthenticator {
private accessToken: string;
Expand All @@ -11,6 +12,26 @@ export class StaticAuthenticator implements IAuthenticator {
this.accessToken = token;
}

public async refreshAccessTokenFromHeader(responseHeaders: HttpHeader[] = []): Promise<string> {
const accessTokenHeader = responseHeaders.find(x => x.name.toLowerCase() === "ocp-apim-sas-token");
if (accessTokenHeader && accessTokenHeader.value) {
const regex = /token=\"(.*)",refresh/gm;
const match = regex.exec(accessTokenHeader.value);

if (!match || match.length < 2) {
console.error(`Token format is not valid.`);
}

const accessToken = `SharedAccessSignature ${accessTokenHeader.value}`;
const current = this.accessToken;
if (current !== accessToken) {
this.accessToken = accessToken;
return accessToken;
}
}
return undefined;
}

public async clearAccessToken(): Promise<void> {
this.accessToken = undefined;
}
Expand Down
7 changes: 7 additions & 0 deletions src/services/mapiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ export class MapiClient {
throw new Error(`Unable to complete request. Error: ${error.message}`);
}

try {
await this.authenticator.refreshAccessTokenFromHeader(response.headers);
}
catch (error) {
console.error("Refresh token error: ", error);
}

return this.handleResponse<T>(response, httpRequest.url);
}

Expand Down
14 changes: 1 addition & 13 deletions src/services/usersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,7 @@ export class UsersService {
});

const identity = response.toObject();
const accessTokenHeader = response.headers.find(x => x.name.toLowerCase() === "ocp-apim-sas-token");

if (accessTokenHeader && accessTokenHeader.value) {
const regex = /token=\"(.*)",refresh/gm;
const match = regex.exec(accessTokenHeader.value);

if (!match || match.length < 2) {
throw new Error(`Token format is not valid.`);
}

const accessToken = match[1];
await this.authenticator.setAccessToken(`SharedAccessSignature ${accessToken}`);
}
await this.authenticator.refreshAccessTokenFromHeader(response.headers);

if (identity && identity.id) {
return identity.id;
Expand Down

0 comments on commit 8acab71

Please sign in to comment.