@humandialog/auth.svelte
Version:
Svelte package to deal with ObjectReef OAuth 2 Identity Provider
1,405 lines (1,397 loc) • 97.4 kB
JavaScript
function noop() { }
function assign(tar, src) {
// @ts-ignore
for (const k in src)
tar[k] = src[k];
return tar;
}
function run(fn) {
return fn();
}
function blank_object() {
return Object.create(null);
}
function run_all(fns) {
fns.forEach(run);
}
function is_function(thing) {
return typeof thing === 'function';
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function is_empty(obj) {
return Object.keys(obj).length === 0;
}
function subscribe(store, ...callbacks) {
if (store == null) {
return noop;
}
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function get_store_value(store) {
let value;
subscribe(store, _ => value = _)();
return value;
}
function component_subscribe(component, store, callback) {
component.$$.on_destroy.push(subscribe(store, callback));
}
function create_slot(definition, ctx, $$scope, fn) {
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);
return definition[0](slot_ctx);
}
}
function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
}
function get_slot_changes(definition, $$scope, dirty, fn) {
if (definition[2] && fn) {
const lets = definition[2](fn(dirty));
if ($$scope.dirty === undefined) {
return lets;
}
if (typeof lets === 'object') {
const merged = [];
const len = Math.max($$scope.dirty.length, lets.length);
for (let i = 0; i < len; i += 1) {
merged[i] = $$scope.dirty[i] | lets[i];
}
return merged;
}
return $$scope.dirty | lets;
}
return $$scope.dirty;
}
function update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) {
if (slot_changes) {
const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);
slot.p(slot_context, slot_changes);
}
}
function get_all_dirty_from_scope($$scope) {
if ($$scope.ctx.length > 32) {
const dirty = [];
const length = $$scope.ctx.length / 32;
for (let i = 0; i < length; i++) {
dirty[i] = -1;
}
return dirty;
}
return -1;
}
function set_store_value(store, ret, value) {
store.set(value);
return ret;
}
function append(target, node) {
target.appendChild(node);
}
function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
}
function detach(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
if (iterations[i])
iterations[i].d(detaching);
}
}
function element(name) {
return document.createElement(name);
}
function text(data) {
return document.createTextNode(data);
}
function space() {
return text(' ');
}
function empty() {
return text('');
}
function listen(node, event, handler, options) {
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
}
function attr(node, attribute, value) {
if (value == null)
node.removeAttribute(attribute);
else if (node.getAttribute(attribute) !== value)
node.setAttribute(attribute, value);
}
function children(element) {
return Array.from(element.childNodes);
}
function set_data(text, data) {
data = '' + data;
if (text.wholeText !== data)
text.data = data;
}
let current_component;
function set_current_component(component) {
current_component = component;
}
const dirty_components = [];
const binding_callbacks = [];
const render_callbacks = [];
const flush_callbacks = [];
const resolved_promise = Promise.resolve();
let update_scheduled = false;
function schedule_update() {
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
}
function tick() {
schedule_update();
return resolved_promise;
}
function add_render_callback(fn) {
render_callbacks.push(fn);
}
// flush() calls callbacks in this order:
// 1. All beforeUpdate callbacks, in order: parents before children
// 2. All bind:this callbacks, in reverse order: children before parents.
// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT
// for afterUpdates called during the initial onMount, which are called in
// reverse order: children before parents.
// Since callbacks might update component values, which could trigger another
// call to flush(), the following steps guard against this:
// 1. During beforeUpdate, any updated components will be added to the
// dirty_components array and will cause a reentrant call to flush(). Because
// the flush index is kept outside the function, the reentrant call will pick
// up where the earlier call left off and go through all dirty components. The
// current_component value is saved and restored so that the reentrant call will
// not interfere with the "parent" flush() call.
// 2. bind:this callbacks cannot trigger new flush() calls.
// 3. During afterUpdate, any updated components will NOT have their afterUpdate
// callback called a second time; the seen_callbacks set, outside the flush()
// function, guarantees this behavior.
const seen_callbacks = new Set();
let flushidx = 0; // Do *not* move this inside the flush() function
function flush() {
// Do not reenter flush while dirty components are updated, as this can
// result in an infinite loop. Instead, let the inner flush handle it.
// Reentrancy is ok afterwards for bindings etc.
if (flushidx !== 0) {
return;
}
const saved_component = current_component;
do {
// first, call beforeUpdate functions
// and update components
try {
while (flushidx < dirty_components.length) {
const component = dirty_components[flushidx];
flushidx++;
set_current_component(component);
update(component.$$);
}
}
catch (e) {
// reset dirty state to not end up in a deadlocked state and then rethrow
dirty_components.length = 0;
flushidx = 0;
throw e;
}
set_current_component(null);
dirty_components.length = 0;
flushidx = 0;
while (binding_callbacks.length)
binding_callbacks.pop()();
// then, once components are updated, call
// afterUpdate functions. This may cause
// subsequent updates...
for (let i = 0; i < render_callbacks.length; i += 1) {
const callback = render_callbacks[i];
if (!seen_callbacks.has(callback)) {
// ...so guard against infinite loops
seen_callbacks.add(callback);
callback();
}
}
render_callbacks.length = 0;
} while (dirty_components.length);
while (flush_callbacks.length) {
flush_callbacks.pop()();
}
update_scheduled = false;
seen_callbacks.clear();
set_current_component(saved_component);
}
function update($$) {
if ($$.fragment !== null) {
$$.update();
run_all($$.before_update);
const dirty = $$.dirty;
$$.dirty = [-1];
$$.fragment && $$.fragment.p($$.ctx, dirty);
$$.after_update.forEach(add_render_callback);
}
}
const outroing = new Set();
let outros;
function group_outros() {
outros = {
r: 0,
c: [],
p: outros // parent group
};
}
function check_outros() {
if (!outros.r) {
run_all(outros.c);
}
outros = outros.p;
}
function transition_in(block, local) {
if (block && block.i) {
outroing.delete(block);
block.i(local);
}
}
function transition_out(block, local, detach, callback) {
if (block && block.o) {
if (outroing.has(block))
return;
outroing.add(block);
outros.c.push(() => {
outroing.delete(block);
if (callback) {
if (detach)
block.d(1);
callback();
}
});
block.o(local);
}
else if (callback) {
callback();
}
}
function create_component(block) {
block && block.c();
}
function mount_component(component, target, anchor, customElement) {
const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor);
if (!customElement) {
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
// if the component was destroyed immediately
// it will update the `$$.on_destroy` reference to `null`.
// the destructured on_destroy may still reference to the old array
if (component.$$.on_destroy) {
component.$$.on_destroy.push(...new_on_destroy);
}
else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
}
after_update.forEach(add_render_callback);
}
function destroy_component(component, detaching) {
const $$ = component.$$;
if ($$.fragment !== null) {
run_all($$.on_destroy);
$$.fragment && $$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
$$.on_destroy = $$.fragment = null;
$$.ctx = [];
}
}
function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
component.$$.dirty.fill(0);
}
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {
const parent_component = current_component;
set_current_component(component);
const $$ = component.$$ = {
fragment: null,
ctx: [],
// state
props,
update: noop,
not_equal,
bound: blank_object(),
// lifecycle
on_mount: [],
on_destroy: [],
on_disconnect: [],
before_update: [],
after_update: [],
context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
// everything else
callbacks: blank_object(),
dirty,
skip_bound: false,
root: options.target || parent_component.$$.root
};
append_styles && append_styles($$.root);
let ready = false;
$$.ctx = instance
? instance(component, options.props || {}, (i, ret, ...rest) => {
const value = rest.length ? rest[0] : ret;
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
if (!$$.skip_bound && $$.bound[i])
$$.bound[i](value);
if (ready)
make_dirty(component, i);
}
return ret;
})
: [];
$$.update();
ready = true;
run_all($$.before_update);
// `false` as a special case of no DOM component
$$.fragment = create_fragment ? create_fragment($$.ctx) : false;
if (options.target) {
if (options.hydrate) {
const nodes = children(options.target);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment.l(nodes);
nodes.forEach(detach);
}
else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment.c();
}
if (options.intro)
transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor, options.customElement);
flush();
}
set_current_component(parent_component);
}
/**
* Base class for Svelte components. Used when dev=false.
*/
class SvelteComponent {
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
$on(type, callback) {
if (!is_function(callback)) {
return noop;
}
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1)
callbacks.splice(index, 1);
};
}
$set($$props) {
if (this.$$set && !is_empty($$props)) {
this.$$.skip_bound = true;
this.$$set($$props);
this.$$.skip_bound = false;
}
}
}
//import base64url from "base64url";
class Token {
_raw;
header;
payload;
constructor(token, parse_as_jwt = true) {
this._raw = token;
if (!parse_as_jwt)
return;
let parts;
parts = token.split('.');
let sheader = parts[0];
let spayload = parts[1];
sheader = this.decode_base64url(sheader);
spayload = this.decode_base64url(spayload);
this.header = JSON.parse(sheader);
this.payload = JSON.parse(spayload);
}
get raw() {
return this._raw;
}
get is_jwt() {
if (this.header == undefined)
return false;
if (this.header == null)
return false;
if (this.header["typ"] === "JWT")
return true;
else
return false;
}
get not_expired() {
let exp;
exp = this.get_claim("exp");
if (exp === undefined)
return false;
let margin_s = 2 * 60; // 2 min default
let iat;
iat = this.get_claim("iat");
if (iat) {
const tokenDuration_min = (exp - iat) / 60;
if (tokenDuration_min > 59) {
margin_s = 5 * 60; // 5 min
}
else if (tokenDuration_min > 29) {
margin_s = 3 * 60; // 3 min
}
else if (tokenDuration_min > 14) {
margin_s = 2 * 60; // 2 min
}
else if (tokenDuration_min > 9) {
margin_s = 1.5 * 60; // 1.5 min
}
else if (tokenDuration_min > 4) {
margin_s = 60; // 1 min
}
else {
margin_s = 15; // 15 sec
}
}
const now = Math.floor(Date.now() / 1000);
if (exp > now + margin_s)
return true;
return false;
}
get_claim(key) {
if (this.payload == undefined)
return undefined;
if (this.payload == null)
return undefined;
let v;
v = this.payload[key];
if (v === undefined)
return undefined;
return v;
}
decode_base64url(input) {
let output = input;
output = output.replace('-', '+');
output = output.replace('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
return "";
}
return atob(output);
}
}
class Browser_storage {
set(key, value, permanent = false) {
if (permanent)
localStorage.setItem(key, value);
else
sessionStorage.setItem(key, value);
}
set_num(key, value, permanent = false) {
if (permanent)
localStorage.setItem(key, value.toString());
else
sessionStorage.setItem(key, value.toString());
}
has(key) {
let v = sessionStorage.getItem(key);
if ((v != undefined) && (v != ""))
return true;
else {
v = localStorage.getItem(key);
if ((v != undefined) && (v != ""))
return true;
else
return false;
}
}
get(key, out) {
let v = sessionStorage.getItem(key);
if ((v != undefined) && (v != "")) {
out(v);
return true;
}
else {
v = localStorage.getItem(key);
if ((v != undefined) && (v != "")) {
out(v);
return true;
}
else
return false;
}
}
get_num(key, out) {
let vs;
const ret = this.get(key, (v) => { vs = v; });
if (ret)
out(parseInt(vs));
return ret;
}
}
const gv = new Browser_storage;
var Mode;
(function (Mode) {
Mode[Mode["Remote"] = 0] = "Remote";
Mode[Mode["Local"] = 1] = "Local";
Mode[Mode["Disabled"] = 2] = "Disabled";
})(Mode || (Mode = {}));
class Local_user {
username;
role = "";
groupId = 0;
uid = 0;
}
class Configuration {
mode = Mode.Disabled;
iss = "";
client_id = "";
client_secret = "";
scope = "";
local_api = "";
local_users = [];
api_version = "v001";
tenant = "";
groups_only = false;
ask_organization_name = true;
let_choose_group_first = false;
refresh_token_persistent = true;
terms_and_conditions_href = "";
privacy_policy_href = "";
}
const subscriber_queue = [];
/**
* Creates a `Readable` store that allows reading by subscription.
* @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions
*/
function readable(value, start) {
return {
subscribe: writable(value, start).subscribe
};
}
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
function writable(value, start = noop) {
let stop;
const subscribers = new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(value));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
function derived(stores, fn, initial_value) {
const single = !Array.isArray(stores);
const stores_array = single
? [stores]
: stores;
const auto = fn.length < 2;
return readable(initial_value, (set) => {
let inited = false;
const values = [];
let pending = 0;
let cleanup = noop;
const sync = () => {
if (pending) {
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
if (auto) {
set(result);
}
else {
cleanup = is_function(result) ? result : noop;
}
};
const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => {
values[i] = value;
pending &= ~(1 << i);
if (inited) {
sync();
}
}, () => {
pending |= (1 << i);
}));
inited = true;
sync();
return function stop() {
run_all(unsubscribers);
cleanup();
};
});
}
class User {
given_name = "";
family_name = "";
picture = "";
email = "";
email_verified = false;
}
class Session {
my_validation_ticket = 0;
_is_active = false;
_user;
_id_token;
_access_token;
_refresh_token;
storage;
appInstanceInfo = null;
configuration;
sessionId;
constructor(storage) {
this.storage = storage;
let arr = new Uint8Array((16) / 2);
window.crypto.getRandomValues(arr);
const dec2hex = (dec) => dec.toString(16).padStart(2, "0");
this.sessionId = Array.from(arr, dec2hex).join('');
}
configure(cfg, internal = false) {
console.log('configure', 'internal', internal, cfg);
this.configuration = new Configuration;
if (cfg) {
switch (cfg.mode) {
case 'remote':
this.configuration.mode = Mode.Remote;
this.configuration.iss = cfg.remote.iss;
this.configuration.client_id = cfg.remote.client_id ?? cfg.remote.clientID;
this.configuration.client_secret = cfg.remote.client_secret ?? cfg.remote.clientSecret;
this.configuration.scope = cfg.remote.scope;
this.configuration.api_version = cfg.remote.api_version ?? cfg.remote.apiVersion ?? "v001";
this.configuration.tenant = cfg.remote.tenant ?? "";
this.configuration.groups_only = cfg.remote.groupsOnly ?? cfg.remote.groups_only ?? false;
this.configuration.ask_organization_name = cfg.remote.ask_organization_name ?? cfg.remote.askOrganizationName ?? true;
this.configuration.refresh_token_persistent = cfg.remote.refresh_token_persistent ?? cfg.remote.refreshTokenPersistent ?? true;
this.configuration.terms_and_conditions_href = cfg.remote.terms_and_conditions_href ?? cfg.remote.termsAndConditionsHRef;
this.configuration.privacy_policy_href = cfg.remote.privacy_policy_href ?? cfg.remote.privacyPolicyHRef;
this.configuration.let_choose_group_first = cfg.remote.let_choose_group_first ?? cfg.remote.letChooseGroupFirst ?? false;
break;
case 'local':
this.configuration.mode = Mode.Local;
this.configuration.local_api = cfg.local.api;
this.configuration.api_version = cfg.local.api_version ?? cfg.local.apiVersion ?? "v001";
this.configuration.local_users = [];
if (cfg.local.users && Array.isArray(cfg.local.users)) {
cfg.local.users.forEach(u => {
switch (typeof u) {
case 'string':
{
const user = new Local_user();
user.username = u;
this.configuration.local_users.push(user);
}
break;
case 'object':
{
const user = new Local_user();
user.username = u.username ?? "";
user.role = u.role ?? "";
user.groupId = u.groupId ?? 0;
user.uid = u.uid ?? 0;
this.configuration.local_users.push(user);
}
break;
}
});
}
break;
case 'disabled':
this.configuration.mode = Mode.Disabled;
this.configuration.local_api = cfg.local.api;
this.configuration.api_version = cfg.local.api_version ?? cfg.local.apiVersion ?? "v001";
break;
}
}
else {
this.configuration.mode = Mode.Disabled;
}
this.setup_mode(this.configuration.mode);
if (!internal) {
this.storage.set('_hd_auth_cfg', JSON.stringify(cfg));
if (this.isValid)
this.boost_validation_ticket();
let new_session = new Session(this.storage);
new_session.clone_from(this);
session.set(new_session); // forces store subscribers
}
}
clone_from(src) {
this.my_validation_ticket = src.my_validation_ticket;
this._is_active = src._is_active;
this._user = src._user;
this._id_token = src._id_token;
this._access_token = src._access_token;
this._refresh_token = src._refresh_token;
this.configuration = src.configuration;
}
get isActive() {
if (!this.isValid)
this.validate();
return this._is_active;
}
get user() {
if (!this.isValid)
this.validate();
return this._user;
}
get idToken() {
if (!this.isValid)
this.validate();
return this._id_token;
}
get accessToken() {
if (!this.isValid)
this.validate();
return this._access_token;
}
get refreshToken() {
if (!this.isValid)
this.validate();
return this._refresh_token;
}
get isValid() {
let ticket;
if (!this.storage.get_num("_hd_auth_session_validation_ticket", (v) => { ticket = v; }))
return false;
return (ticket == this.my_validation_ticket);
}
get apiAddress() {
let res;
if (this.storage.get("_hd_auth_api_address", (v) => { res = v; }))
return res;
else
return "";
}
get tid() {
let res;
if (this.storage.get("_hd_auth_tenant", (v) => { res = v; }))
return res;
else
return "";
}
get appId() {
let scopes = this.configuration.scope.split(' ');
if (!scopes.length)
return '';
//remove predefined scopes
scopes = scopes.filter(s => (s != 'openid') && (s != 'profile') && (s != 'email') && (s != 'address') && (s != 'phone'));
if (!scopes.length)
return '';
let app_id = scopes[0];
return app_id;
}
get tenants() {
let res;
if (!this.storage.get("_hd_signedin_tenants", (v) => { res = v; }))
return [];
if (!res)
return [];
const tenants = JSON.parse(res);
return tenants;
}
set tenants(infos) {
const tInfos = JSON.stringify(infos);
this.storage.set("_hd_signedin_tenants", tInfos, false);
}
get isUnauthorizedGuest() {
let result = false;
let res;
if (!this.storage.get("_hd_auth_unauthorized_guest", (v) => { res = v; }))
result = false;
else if (res == "1")
result = true;
else
result = false;
return result;
}
set isUnauthorizedGuest(val) {
this.storage.set("_hd_auth_unauthorized_guest", val ? "1" : "", true);
}
validate() {
if (!this.storage.get_num("_hd_auth_session_validation_ticket", (v) => { this.my_validation_ticket = v; })) {
this.my_validation_ticket = 1;
this.storage.set_num("_hd_auth_session_validation_ticket", this.my_validation_ticket);
}
if (!this.configuration) {
let cfg_json;
if (this.storage.get('_hd_auth_cfg', (v) => cfg_json = v)) {
try {
let cfg = JSON.parse(cfg_json);
this.configure(cfg, true);
}
catch (err) {
console.error(err);
}
}
}
if (this.disabled) {
this.setCurrentTenantAPI(this.configuration.local_api, '');
this._is_active = true;
return;
}
else if (this.local) {
if (this.localDevCurrentUser) {
this._is_active = true;
}
else {
this._is_active = false;
}
this.setCurrentTenantAPI(this.configuration.local_api, '');
return;
}
this._is_active = false;
let token;
if (this.storage.get("_hd_auth_id_token", (v) => { token = v; })) {
this._id_token = new Token(token);
this._user = new User();
this._user.given_name = this._id_token.get_claim("given_name");
this._user.family_name = this._id_token.get_claim("family_name");
this._user.picture = this._id_token.get_claim("picture");
this._user.email = this._id_token.get_claim("email");
this._user.email_verified = this._id_token.get_claim("email_verified");
}
else
this._id_token = null;
if (this.storage.get("_hd_auth_access_token", (v) => { token = v; }))
this._access_token = new Token(token);
else
this._access_token = null;
if (this.storage.get("_hd_auth_refresh_token", (v) => { token = v; }))
this._refresh_token = new Token(token, false);
else
this._refresh_token = null;
if ((this._access_token != null) || (this._id_token != null))
this._is_active = true;
}
refreshTokens(tokens_info, chosen_tenant_id = undefined) {
if (!tokens_info.access_token)
return false;
if (!tokens_info.id_token)
return false;
if (!tokens_info.refresh_token)
return false;
this.storage.set("_hd_auth_id_token", tokens_info.id_token);
this.storage.set("_hd_auth_access_token", tokens_info.access_token);
this.storage.set("_hd_auth_refresh_token", tokens_info.refresh_token, this.configuration.refresh_token_persistent);
this._id_token = new Token(tokens_info.id_token);
this._user = new User();
this._user.given_name = this._id_token.get_claim("given_name");
this._user.family_name = this._id_token.get_claim("family_name");
this._user.picture = this._id_token.get_claim("picture");
this._user.email = this._id_token.get_claim("email");
this._user.email_verified = this._id_token.get_claim("email_verified");
this._access_token = new Token(tokens_info.access_token);
this._refresh_token = new Token(tokens_info.refresh_token, false);
this._is_active = true;
if (tokens_info.tenant != undefined) {
this.setCurrentTenantAPI(tokens_info.tenant.url, tokens_info.tenant.id);
this.tenants = [tokens_info.tenant];
}
else if ((tokens_info.tenants != undefined) && (tokens_info.tenants.length > 0)) {
if (tokens_info.tenants.length == 1)
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
else {
if (chosen_tenant_id) {
const chosen_tenant = tokens_info.tenants.find(el => el.id == chosen_tenant_id);
if (chosen_tenant)
this.setCurrentTenantAPI(chosen_tenant.url, chosen_tenant.id);
else
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
}
else
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
}
this.tenants = tokens_info.tenants;
}
else
return false;
return true;
}
signin(tokens_info, chosen_tenant_id = undefined) {
if ((tokens_info.access_token == undefined) || (tokens_info.access_token == "")) {
this.signout();
return true;
}
if ((tokens_info.id_token == undefined) || (tokens_info.id_token == "")) {
this.signout();
return true;
}
if ((tokens_info.refresh_token == undefined) || (tokens_info.refresh_token == "")) {
this.signout();
return true;
}
this.storage.set("_hd_auth_access_token", tokens_info.access_token);
this.storage.set("_hd_auth_id_token", tokens_info.id_token);
this.storage.set("_hd_auth_refresh_token", tokens_info.refresh_token, this.configuration.refresh_token_persistent);
if (tokens_info.tenant != undefined) {
this.setCurrentTenantAPI(tokens_info.tenant.url, tokens_info.tenant.id);
this.tenants = [tokens_info.tenant];
}
else if ((tokens_info.tenants != undefined) && (tokens_info.tenants.length > 0)) {
if (tokens_info.tenants.length == 1)
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
else {
if (chosen_tenant_id) {
const chosen_tenant = tokens_info.tenants.find(el => el.id == chosen_tenant_id);
if (chosen_tenant)
this.setCurrentTenantAPI(chosen_tenant.url, chosen_tenant.id);
else
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
}
else
this.setCurrentTenantAPI(tokens_info.tenants[0].url, tokens_info.tenants[0].id);
}
this.tenants = tokens_info.tenants;
}
else if ((tokens_info.apps != undefined) && (tokens_info.apps.length > 0)) {
// todo: multi app not supported yet?
this.signout();
return false;
}
else {
this.signout();
return false;
}
this.boost_validation_ticket();
this.validate();
this.checkServerAndClientTimeMismatch();
let new_session = new Session(this.storage);
new_session.clone_from(this);
session.set(new_session); // forces store subscribers
return true;
}
checkServerAndClientTimeMismatch() {
if (!this._access_token)
return;
const serverTime = this._access_token.get_claim("iat");
if (!serverTime)
return;
const clientTime = Math.floor(Date.now() / 1000);
const timeShift = clientTime - serverTime;
// now just logging. In the near future we need to store this value and use on Token::not_expired property
console.log('Server/Client time mismatch: ', timeShift);
}
boost_validation_ticket() {
let validation_ticket = 0;
this.storage.get_num("_hd_auth_session_validation_ticket", (v) => { validation_ticket = v; });
validation_ticket++;
this.storage.set_num("_hd_auth_session_validation_ticket", validation_ticket);
this.my_validation_ticket = validation_ticket;
}
setCurrentTenantAPI(url, tid) {
this.storage.set("_hd_auth_api_address", url);
this.storage.set("_hd_auth_tenant", tid);
this.storage.set('_hd_auth_last_chosen_tenant_id', tid, true);
}
get lastChosenTenantId() {
let res;
if (!this.storage.get('_hd_auth_last_chosen_tenant_id', (v) => res = v))
return '';
return res;
}
signout() {
this.storage.set("_hd_auth_id_token", "");
this.storage.set("_hd_auth_access_token", "");
this.storage.set("_hd_auth_refresh_token", "", this.configuration.refresh_token_persistent);
this.storage.set("_hd_auth_api_address", "");
this.storage.set("_hd_auth_tenant", "");
this.storage.set("_hd_auth_local_dev_user", "");
this.storage.set('_hd_auth_unauthorized_guest', "", true);
this._id_token = null;
this._access_token = null;
this._refresh_token = null;
this._is_active = false;
this.boost_validation_ticket();
let new_session = new Session(this.storage);
new_session.clone_from(this);
session.set(new_session); // forces store subscribers
}
appAccessRole() {
if (!this.configuration)
return '';
const scopes = this.configuration.scope.split(' ');
if ((!scopes) || scopes.length == 0)
return '';
const appId = scopes[scopes.length - 1];
if (!this.isActive)
return '';
const token = this.accessToken;
if (token == undefined)
return '';
if (token == null)
return '';
if (!token.raw)
return '';
if (!token.is_jwt)
return '';
const access = token.payload['access'];
if (!!access &&
access.length > 0) {
const scopeIdx = access.findIndex(e => e['app'] == appId);
if (scopeIdx < 0)
return '';
const accessScope = access[scopeIdx];
const scopeTenants = accessScope['tenants'];
if (!scopeTenants || scopeTenants.length == 0)
return '';
for (let i = 0; i < scopeTenants.length; i++) {
const tenantInfo = scopeTenants[i];
if (typeof tenantInfo === 'object' && tenantInfo !== null) {
if (tenantInfo['tid'] == this.tid) {
if (!tenantInfo.details)
return '';
const accessDetails = JSON.parse(tenantInfo.details);
return accessDetails.role ?? '';
}
}
}
return '';
}
else
return '';
}
authAccessGroup() {
return this.accessGroup("auth");
}
filesAccessGroup() {
return this.accessGroup("files");
}
accessGroup(scope) {
if (!this.isActive)
return 0;
const token = this.accessToken;
if (token == undefined)
return 0;
if (token == null)
return 0;
if (!token.raw)
return 0;
if (!token.is_jwt)
return 0;
const access = token.payload['access'];
if (!!access &&
access.length > 0) {
const scopeIdx = access.findIndex(e => e['app'] == scope);
if (scopeIdx < 0)
return 0;
const accessScope = access[scopeIdx];
const scopeTenants = accessScope['tenants'];
if (!scopeTenants || scopeTenants.length == 0)
return 0;
for (let i = 0; i < scopeTenants.length; i++) {
const tenantInfo = scopeTenants[i];
if (typeof tenantInfo === 'object' && tenantInfo !== null) {
if (tenantInfo['tid'] == this.tid)
return tenantInfo['gid'] ?? 0;
}
}
return 0;
}
else
return 0;
}
async __is_admin() {
if (!this.isValid)
this.validate();
if (!this.isActive)
return false;
if (this.tid == "")
return false;
let path;
path = this.configuration.iss + "/auth/am_i_admin";
path += "?tenant=" + this.tid;
const res = await fetch(path, {
method: 'get',
headers: new Headers({
'Authorization': 'Bearer ' + this._access_token.raw,
'Accept': 'application/json'
})
});
if (!res.ok)
return false;
const result = await res.json();
return result.response === true;
}
get mode() {
let num_mode = 0;
if (!this.storage.get_num('_hd_auth_session_mode', (v) => { num_mode = v; }))
return Mode.Remote;
else
switch (num_mode) {
case 0:
return Mode.Remote;
case 1:
return Mode.Local;
case 2:
return Mode.Disabled;
default:
return Mode.Remote;
}
}
set mode(m) {
switch (m) {
case Mode.Remote:
this.storage.set_num("_hd_auth_session_mode", 0);
break;
case Mode.Local:
this.storage.set_num("_hd_auth_session_mode", 1);
break;
case Mode.Disabled:
this.storage.set_num("_hd_auth_session_mode", 2);
break;
}
}
get remote() {
return (this.mode == Mode.Remote);
}
get local() {
return (this.mode == Mode.Local);
}
get disabled() {
return (this.mode == Mode.Disabled);
}
setup_mode(m) {
this.mode == Mode.Remote;
//this.signout();
this.mode = m;
if (m == Mode.Remote) ;
else {
if (this.configuration && this.configuration.local_api) ;
}
}
setLocalDevCurrentUser(email) {
this.storage.set('_hd_auth_local_dev_user', email);
this.boost_validation_ticket();
this.validate();
let new_session = new Session(this.storage);
new_session.clone_from(this);
session.set(new_session); // forces store subscribers
}
get localDevCurrentUser() {
let email;
this.storage.get('_hd_auth_local_dev_user', (v) => { email = v; });
if (!email)
return null;
const foundUser = this.configuration.local_users.find(u => u.username == email);
return foundUser;
}
}
const session = writable(new Session(gv));
let refreshing = false;
class reef {
static configure(cfg) {
let _session = get_store_value(session);
_session.configure(cfg);
}
static async fetch(...args) {
let [resource, options] = args;
let given_url = '';
given_url = resource;
let _session = get_store_value(session);
// check if resource is absolute url, if not we add session.apiAddress obtained with auth tokens
let absolute_pattern = /^https?:\/\//i;
if (!absolute_pattern.test(given_url)) {
let full_path = _session.apiAddress;
if (full_path.endsWith('/')) {
if (given_url.startsWith('/'))
full_path = full_path + given_url.substr(1);
else
full_path = full_path + given_url;
}
else {
if (given_url.startsWith('/'))
full_path = full_path + given_url;
else
full_path = full_path + '/' + given_url;
}
resource = full_path;
}
if ((options == undefined) || (options == null))
options = {};
if ((options.headers == undefined) || (options.headers == null))
options.headers = new Headers();
if (!options.headers.has("Authorization")) {
if (_session.accessToken != null) {
if (!_session.accessToken.not_expired) {
console.log('sessionId:', _session.sessionId);
const iat = _session.accessToken.get_claim("iat");
const exp = _session.accessToken.get_claim("exp");
console.log('iat:', iat, new Date(iat));
console.log('exp:', exp, new Date(exp));
if (refreshing) {
console.log('auth: request need to wait for tokens refreshing... ', resource);
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
let triesNo = 10;
while (refreshing && triesNo > 0) {
await sleep(1000);
triesNo--;
}
if (refreshing) {
console.log('auth: too long refresh waiting. drop the request');
return null;
}
}
else {
console.log('auth: first req with expired token, refreshing...', resource);
refreshing = true;
const refreshingSuccess = await this.refreshTokens(_session);
refreshing = false;
if (!refreshingSuccess)
this.redirectToSignIn();
}
}
options.headers.append("Authorization", "Bearer " + _session.accessToken.raw);
}
else {
const user = _session.localDevCurrentUser;
if (user) {
if (user.uid > 0) {
if (!options.headers.has("X-Reef-User-Id"))
options.headers.append('X-Reef-User-Id', user.uid);
}
else {
if (!options.headers.has("X-Reef-As-User"))
options.headers.append('X-Reef-As-User', user.username);
}
if (user.role) {
if (!options.headers.has("X-Reef-Access-Role"))
options.headers.append('X-Reef-Access-Role', user.role);
}
if (user.groupId) {
if (!options.headers.has("X-Reef-Group-Id"))
options.headers.append('X-Reef-Group-Id', user.groupId);
}
}
}
}
if (_session.tenants.length > 0) {
const tenantInfo = _session.tenants.find(t => t.id == _session.tid);
if (tenantInfo && tenantInfo.headers && tenantInfo.headers.length > 0) {
tenantInfo.headers.forEach(h => options.headers.append(h.key, h.value));
}
}
return fetch(resource, options);
}
static correct_path_with_api_version_if_needed(path) {
if (path.startsWith('/json/'))
return path;
let apiver = 'v001'; // default
let _session = get_store_value(session);
if (_session && _session.configuration && _session.configuration.api_version)
apiver = _session.configuration.api_version;
if (path.startsWith('/'))
return `/json/${apiver}${path}`;
else
return `/json/${apiver}/${path}`;
}
static async get(_path, onError) {
let path = reef.correct_path_with_api_version_if_needed(_path);
try {
let res = await reef.fetch(path, {});
if (res.ok) {
const response_string = await res.text();
if (!response_string)
return {};
else
return JSON.parse(response_string);
}
else {
const err = await res.text();
console.error(err);
if (onError)
onError(err);
return null;
}
}
catch (err) {
console.error(err);
if (onError)
onError(err);
return null;
}
}
static async post(_path, request_object, onError) {
let path = reef.correct_path_with_api_version_if_needed(_path);
try {
let res = await reef.fetch(path, {
method: 'POST',
body: JSON.stringify(request_object)
});
if (res.ok) {
const response_string = await res.text();
if (!response_string)
return {};
else
return JSON.parse(response_string);
}
else {
const err = await res.text();
console.error(err);
if (onError)
onError(err);
return null;
}
}