@pecureo/anvil-stream
Version:
Stream-Based Authorization framework for Anvil Connect
128 lines (108 loc) • 3.2 kB
text/typescript
import { Observable, interval } from 'rxjs';
import {
distinctUntilChanged, map, shareReplay, startWith, tap
} from 'rxjs/operators';
import { Anvil } from './anvil';
import { IAuthorizationOptions, Issuer } from './issuer';
import { Scopes } from './scopes';
interface ICommonClaims {
aud: string;
exp: number;
iat: number;
iss: string;
sub: string;
}
export interface IAccessClaims extends ICommonClaims {
jti: string;
scope: string;
}
export interface IIdClaims extends ICommonClaims {
at_hash: string;
nonce: string;
}
export interface ISessionData {
access_claims: IAccessClaims;
access_token: string;
id_claims: IIdClaims;
id_token: string;
expires_in: string;
token_type: string;
options?: IAuthorizationOptions;
session_state?: string;
state?: string;
user_info?: Object;
}
export class Session {
public access_token: string;
public expiration_date: Date;
public expired$: Observable<boolean>;
public scopes: Scopes;
public subject: string;
constructor(private issuer: Issuer, private session_data?: ISessionData) {
if(session_data) {
this.setTokenData(
session_data.access_token,
session_data.id_claims.exp,
session_data.access_claims.scope,
session_data.access_claims.sub
);
} else {
this.setTokenData('', 0, '', '');
}
this.expired$ = interval(0.5).pipe(
map(() => this.expired),
distinctUntilChanged(),
startWith(this.expired),
// This is only acceptable as long as the `distinctUntilChanged` call is
// in this chain! If it is removed `abort` will keep being called every
// emission of the `Observable` once the `Session` expires
tap(expired => {
if(expired) {
this.abort();
}
}),
shareReplay(1)
);
}
abort() {
if(this.session_data === Anvil.session) {
if(this.session_data.options.force_path) {
// As the regular reset method only removes the cookie containing the
// secret for the current path, the one for the forced path has to be
// removed manually
let expires = new Date(0).toUTCString();
let path = decodeURIComponent(this.session_data.options.force_path);
document.cookie = `anvil.connect=; expires=${expires}; path=${path}`;
}
Anvil.reset();
}
// Force the session to be 'expired' as it's no longer valid when aborted
this.setTokenData('', 0, '', '');
this.issuer.removeSession(this);
}
get expired(): boolean {
return this.expiration_date <= new Date();
}
get persisted(): boolean {
return this.issuer.persisted_session === this;
}
get user_info() {
if(this.session_data) {
return this.session_data.user_info;
} else {
return null;
}
}
private setTokenData(
access_token: string,
expiration_date_s: number,
scopes: string,
subject: string
) {
this.access_token = access_token;
// Token expirations are in seconds, JavaScript operates using milliseconds
this.expiration_date = new Date(expiration_date_s * 1000);
this.scopes = new Scopes(scopes);
this.subject = subject;
}
}