@upbond/auth-spa-js
Version:
Auth SDK for Single Page Applications using Authorization Code Grant Flow with PKCE
198 lines (159 loc) • 4.58 kB
text/typescript
import { IdToken } from './global';
interface CacheKeyData {
audience: string;
scope: string;
client_id: string;
}
interface DecodedToken {
claims: IdToken;
user: any;
}
interface CacheEntry {
id_token: string;
access_token: string;
expires_in: number;
decodedToken: DecodedToken;
audience: string;
scope: string;
client_id: string;
refresh_token?: string;
}
export interface ICache {
save(entry: CacheEntry): void;
get(key: CacheKeyData, expiryAdjustmentSeconds?: number): Partial<CacheEntry>;
clear(): void;
}
const keyPrefix = '@@authspajs@@';
const DEFAULT_EXPIRY_ADJUSTMENT_SECONDS = 0;
const createKey = (e: CacheKeyData) =>
`${keyPrefix}::${e.client_id}::${e.audience}::${e.scope}`;
type CachePayload = {
body: Partial<CacheEntry>;
expiresAt: number;
};
/**
* Wraps the specified cache entry and returns the payload
* @param entry The cache entry to wrap
*/
const wrapCacheEntry = (entry: CacheEntry): CachePayload => {
const expiresInTime = Math.floor(Date.now() / 1000) + entry.expires_in;
// const expirySeconds = Math.min(expiresInTime, entry.decodedToken.claims.exp);
return {
body: entry,
expiresAt: expiresInTime
};
};
export class LocalStorageCache implements ICache {
public save(entry: CacheEntry): void {
// console.log(entry);
const cacheKey = createKey(entry);
// console.log('cacheKey', cacheKey);
const payload = wrapCacheEntry(entry);
window.localStorage.setItem(cacheKey, JSON.stringify(payload));
}
public get(
key: CacheKeyData,
expiryAdjustmentSeconds = DEFAULT_EXPIRY_ADJUSTMENT_SECONDS
): Partial<CacheEntry> {
const cacheKey = createKey(key);
const payload = this.readJson(cacheKey);
const nowSeconds = Math.floor(Date.now() / 1000);
if (!payload) return;
if (payload.expiresAt - expiryAdjustmentSeconds < nowSeconds) {
if (payload.body.refresh_token) {
const newPayload = this.stripData(payload);
this.writeJson(cacheKey, newPayload);
return newPayload.body;
}
localStorage.removeItem(cacheKey);
return;
}
return payload.body;
}
public clear() {
for (var i = localStorage.length - 1; i >= 0; i--) {
if (localStorage.key(i).startsWith(keyPrefix)) {
localStorage.removeItem(localStorage.key(i));
}
}
}
/**
* Retrieves data from local storage and parses it into the correct format
* @param cacheKey The cache key
*/
private readJson(cacheKey: string): CachePayload {
const json = window.localStorage.getItem(cacheKey);
let payload;
if (!json) {
return;
}
payload = JSON.parse(json);
if (!payload) {
return;
}
return payload;
}
/**
* Writes the payload as JSON to localstorage
* @param cacheKey The cache key
* @param payload The payload to write as JSON
*/
private writeJson(cacheKey: string, payload: CachePayload) {
localStorage.setItem(cacheKey, JSON.stringify(payload));
}
/**
* Produce a copy of the payload with everything removed except the refresh token
* @param payload The payload
*/
private stripData(payload: CachePayload): CachePayload {
const { refresh_token } = payload.body;
const strippedPayload: CachePayload = {
body: { refresh_token: refresh_token },
expiresAt: payload.expiresAt
};
return strippedPayload;
}
}
export class InMemoryCache {
public enclosedCache: ICache = (function () {
let cache: CachePayload = {
body: {},
expiresAt: 0
};
return {
save(entry: CacheEntry) {
const key = createKey(entry);
const payload = wrapCacheEntry(entry);
cache[key] = payload;
},
get(
key: CacheKeyData,
expiryAdjustmentSeconds = DEFAULT_EXPIRY_ADJUSTMENT_SECONDS
) {
const cacheKey = createKey(key);
const wrappedEntry: CachePayload = cache[cacheKey];
const nowSeconds = Math.floor(Date.now() / 1000);
if (!wrappedEntry) {
return;
}
if (wrappedEntry.expiresAt - expiryAdjustmentSeconds < nowSeconds) {
if (wrappedEntry.body.refresh_token) {
wrappedEntry.body = {
refresh_token: wrappedEntry.body.refresh_token
};
return wrappedEntry.body;
}
delete cache[cacheKey];
return;
}
return wrappedEntry.body;
},
clear() {
cache = {
body: {},
expiresAt: 0
};
}
};
})();
}