Skip to content

Commit

Permalink
Auth fix
Browse files Browse the repository at this point in the history
* Fixed issue when access token expires
* Fixed issue when refresh token is revoked
* Fixed mobile styles
* Updated libraries
* Added link to hosted app
  • Loading branch information
joakimgunst authored Nov 8, 2017
1 parent 3dc3867 commit e618822
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 461 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"prettier.tabWidth": 2,
"editor.tabSize": 2
"editor.tabSize": 2,
"typescript.tsdk": "node_modules\\typescript\\lib"
}
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ This is an example application for the [`optimaze-viewer`](https://github.com/ra

Note: The API which this application uses is not yet published but will be published later in 2017.

## Demo

This app is hosted at
[https://optimazeviewerexample.azurewebsites.net/](https://optimazeviewerexample.azurewebsites.net/)

## Usage

1. Run `yarn` to install dependencies
2. Run `yarn start` to start dev server
1. Run `yarn start` to start dev server

Alternatively, run `yarn build` to create a production build
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
},
"dependencies": {
"@rapal/optimaze-viewer": "^0.3.0",
"@types/date-fns": "^2.6.0",
"@types/es6-promise": "^0.0.33",
"@types/jwt-decode": "^2.2.1",
"@types/leaflet": "^1.2.0",
Expand Down
12 changes: 8 additions & 4 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ body {

#viewer {
height: 100vh;
z-index: 0;
}

.leaflet-container {
Expand All @@ -16,17 +17,20 @@ body {
top: 50%;
left: 50%;
font-size: 24px;
width: 100px;
margin-left: -50px;
width: 140px;
margin-left: -70px;
}

.user-info {
position: absolute;
top: 10px;
right: 10px;
background: #eee;
border-radius: 3px;
padding: 10px;
z-index: 1;
background: #eee;
border: 2px solid rgba(0,0,0,.2);
border-radius: 4px;
background-clip: padding-box;
}

.logout-button {
Expand Down
28 changes: 13 additions & 15 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import loadViewer from "./viewer";
import { getAccessToken, showLoginButton, showUserInfo } from "./authentication";
import { loadViewer } from "./viewer";
import { getAccessToken, showUserInfo, showLogin } from "./authentication";

import "leaflet/dist/leaflet.css";
import "./app.css";
Expand All @@ -8,16 +8,14 @@ import "./app.css";
const companyId = 1361;
const floorId = "m2033670";

// Get authorization code from URL
const params = new URLSearchParams(document.location.search);
const authorizationCode = params.get("code");
if (authorizationCode) {
window.history.replaceState(null, "", window.location.pathname);
}

getAccessToken(authorizationCode)
.then(accessToken => {
loadViewer(companyId, floorId, accessToken);
showUserInfo(accessToken);
})
.catch(() => showLoginButton());
getAccessToken().then(
() => {
// Access token available, load viewer
loadViewer(companyId, floorId);
showUserInfo();
},
() => {
// Access token unavailable, user must log in
showLogin();
}
);
45 changes: 32 additions & 13 deletions src/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import { oauthUrl, clientId, clientSecret, scope } from "./config";
const redirectUri = document.location.href.split("?")[0];

/**
* Gets access token from local storage or by requesting new token.
* Gets access token from local storage, by requesting new token,
* or by reading authorization code from URL parameter.
* Throws error if no valid token can be returned.
*/
export async function getAccessToken(
authorizationCode: string | null
): Promise<string> {
export async function getAccessToken(): Promise<string> {
const accessToken = window.localStorage.getItem("access_token");
const accessTokenExpires = window.localStorage.getItem(
"access_token_expires"
Expand All @@ -30,13 +29,25 @@ export async function getAccessToken(
}

// 3. Authorization code is available, get refresh and access tokens
const authorizationCode = getAuthorizationCode();
if (authorizationCode) {
return await getRefreshAndAccessTokens(authorizationCode);
}

throw new Error(
"Cannot get access code. Make sure 'authorization_code' is saved to session storage."
);
// 4. Cannot get access token, user needs to log in
throw new Error("Cannot get access token. User needs to log in.");
}

/**
* Gets the authorization code from the `code` URL parameter and removes it from the URL.
*/
function getAuthorizationCode() {
const params = new URLSearchParams(document.location.search);
const authorizationCode = params.get("code");
if (authorizationCode) {
window.history.replaceState(null, "", window.location.pathname);
}
return authorizationCode;
}

/**
Expand Down Expand Up @@ -146,17 +157,17 @@ function login() {
/**
* Logs out the user by clearing all tokens from local storage and reloading the page.
*/
function logout() {
window.localStorage.removeItem("refresh_token");
window.localStorage.removeItem("access_token");
window.localStorage.removeItem("access_token_expires");
export function logout() {
clearTokens();
window.location.reload();
}

/**
* Shows login button which takes the user to the authorize page.
*/
export function showLoginButton() {
export function showLogin() {
clearTokens();

const loginButton = document.createElement("button");
loginButton.innerText = "Log in";
loginButton.setAttribute("class", "login-button");
Expand All @@ -165,10 +176,18 @@ export function showLoginButton() {
document.body.appendChild(loginButton);
}

function clearTokens() {
window.localStorage.removeItem("refresh_token");
window.localStorage.removeItem("access_token");
window.localStorage.removeItem("access_token_expires");
}

/**
* Adds an element to the body that shows the currently logged in user and a logout link.
*/
export function showUserInfo(accessToken: string) {
export async function showUserInfo() {
const accessToken = await getAccessToken();

const jwt: User = jwtDecode(accessToken);

const userInfo = document.createElement("div");
Expand Down
74 changes: 74 additions & 0 deletions src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { apiUrl } from "./config";
import { getAccessToken, logout } from "./authentication";
import {
GraphicsLayer,
Dimensions,
Boundary,
TileCoordinates
} from "@rapal/optimaze-viewer";

export async function getFloorGraphics(companyId: number, floorId: string) {
const url = `${apiUrl}/${companyId}/floors/${floorId}/graphics`;
return getJson<FloorGraphics>(url);
}

export async function getSeats(companyId: number, floorId: string) {
const url = `${apiUrl}/${companyId}/seats?floorId=${floorId}`;
return getJson<List<Seat>>(url);
}

export async function getTile(
companyId: number,
floorId: string,
layer: GraphicsLayer,
coordinates: TileCoordinates
): Promise<string> {
const url =
`${apiUrl}/${companyId}/floors/${floorId}/tiles?` +
`layer=${layer}&x=${coordinates.x}&y=${coordinates.y}&z=${coordinates.z}`;
return getJson<string>(url);
}

/**
* Authenticated JSON request.
*/
async function getJson<TData>(url: string): Promise<TData> {
try {
const accessToken = await getAccessToken();

const response = await fetch(url, {
headers: [["Authorization", "Bearer " + accessToken]]
});

if (response.ok) {
return response.json();
} else {
throw new Error(response.statusText);
}
} catch (e) {
logout();
throw e;
}
}

interface FloorGraphics {
dimensions: Dimensions;
graphicsLayers: GraphicsLayer[];
spaceGraphics: SpaceGraphics[];
scale: number;
}

interface SpaceGraphics {
id: string;
boundaries: Boundary[];
}

interface List<TItem> {
items: TItem[];
}

interface Seat {
id: number;
x: number;
y: number;
}
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

<head>
<title>optimaze-viewer-example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
Expand Down
Loading

0 comments on commit e618822

Please sign in to comment.