UNPKG

@humandialog/auth.svelte

Version:

Svelte package to deal with ObjectReef OAuth 2 Identity Provider

1,405 lines (1,397 loc) 97.4 kB
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; } }