baqend
Version:
Baqend JavaScript SDK
150 lines (130 loc) • 4.84 kB
text/typescript
import { hmac } from '../util';
import type { GlobalStorage } from './GlobalStorage';
import type { WebStorage } from './WebStorage';
export interface TokenData {
val: any;
sig: string;
createdAt: number;
data: string;
expireAt: number;
}
export interface TokenStorageFactory {
/**
* Creates a new tokenStorage which persist tokens for the given origin
* @param origin The origin where the token contains to
* @return The initialized token storage
*/
create(origin: string): Promise<TokenStorage>
}
export class TokenStorage {
static GLOBAL: typeof GlobalStorage;
static WEB_STORAGE: typeof WebStorage;
static hmac = hmac;
/**
* The actual stored token
*/
tokenData: TokenData | null;
/**
* The origin of the token
*/
origin: string;
/**
* Indicates if the token should keep temporary only or should be persisted for later sessions
*/
temporary: boolean;
/**
* Parse a token string in its components
* @param token The token string to parse, time values are returned as timestamps
* @return The parsed token data
*/
static parse(token: string): TokenData {
return {
val: token,
createdAt: parseInt(token.substring(0, 8), 16) * 1000,
expireAt: parseInt(token.substring(8, 16), 16) * 1000,
sig: token.substring(token.length - 40),
data: token.substring(0, token.length - 40),
};
}
/**
* Get the stored token
* @return The token or undefined, if no token is available
*/
get token(): string {
return this.tokenData ? this.tokenData.val : null;
}
static create(origin: string) {
return Promise.resolve(new TokenStorage(origin));
}
/**
* @param origin The origin where the token belongs to
* @param token The initial token
* @param temporary If the token should be saved temporary or permanently
*/
constructor(origin: string, token?: string | null, temporary?: boolean) {
this.tokenData = token ? TokenStorage.parse(token) : null;
this.origin = origin;
this.temporary = !!temporary;
}
/**
* Use the underlying storage implementation to save the token
* @param origin The origin where the token belongs to
* @param token The initial token
* @param temporary If the token should be saved temporary or permanently
* @return
*/
protected saveToken(origin: string, token: string | null, temporary: boolean): void {
// eslint-disable-next-line no-underscore-dangle
if (this._saveToken !== TokenStorage.prototype._saveToken) {
// eslint-disable-next-line no-console
console.log('Using deprecated TokenStorage._saveToken implementation.');
// eslint-disable-next-line no-underscore-dangle
this._saveToken(origin, token, temporary);
}
}
/**
* Use the underlying storage implementation to save the token
* @param origin The origin where the token belongs to
* @param token The initial token
* @param temporary If the token should be saved temporary or permanently
* @return
* @deprecated Use TokenStorage#saveToken instead
* @protected
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected _saveToken(origin: string, token: string | null, temporary: boolean): void {}
/**
* Update the token for the givin origin, the operation may be asynchronous
* @param token The token to store or <code>null</code> to remove the token
*/
update(token: string | null) {
const t = token ? TokenStorage.parse(token) : null;
if (this.tokenData && t && this.tokenData.expireAt > t.expireAt) {
// an older token was fetched from the cache, so ignore it
return;
}
this.tokenData = t;
this.saveToken(this.origin, token, this.temporary);
}
/**
* Derives a resource token from the stored origin token and signs the resource with the generated resource token
*
* @param resource The resource which will be accessible with the returned token
* @param sign Sign the given resource with a token, if sign is false the resource will only be encoded to a path
* @return A resource token which can only be used to access the specified resource
*/
signPath(resource: string, sign: boolean = true): Promise<string> {
const { tokenData } = this;
const result = Promise.resolve(resource.split('/').map(encodeURIComponent).join('/'));
if (!tokenData || !sign) {
return result;
}
return result.then((path) => TokenStorage.hmac(path + tokenData.data, tokenData.sig)
.then((hash) => `${path}?BAT=${tokenData.data + hash}`))
.catch((e) => {
// eslint-disable-next-line no-console
console.warn('Can\'t sign the resource, run the SDK on a secured origin, or provide an alternative hmac implementation on TokenStorage.hmac', e);
return result;
});
}
}