UNPKG

@humandialog/auth.svelte

Version:

Svelte package to deal with ObjectReef OAuth 2 Identity Provider

619 lines 24.8 kB
import { Token } from "./Token"; import { gv } from "./Storage"; import { Configuration, Mode, Local_user } from "./Configuration"; import { writable } from 'svelte/store'; export class User { given_name = ""; family_name = ""; picture = ""; email = ""; email_verified = false; } export class Header_info { key; value; } export class Tenant_info { id; url; name = ""; headers; } export class App_instance_info { tenant_id = ""; name = ""; desc = ""; img = ""; is_public = false; unauthorized_guest_allowed = false; } export 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; if (typeof globalThis !== "undefined" && globalThis.crypto && typeof globalThis.crypto.getRandomValues === "function") { let arr = new Uint8Array((16) / 2); globalThis.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() { console.log('auth: session 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; } checkStorageConsistency() { let error = false; error = this.checkStoredKey('_hd_auth_cfg') || error; error = this.checkStoredKey('_hd_signedin_tenants') || error; error = this.checkStoredKey('_hd_auth_id_token') || error; error = this.checkStoredKey('_hd_auth_access_token') || error; error = this.checkStoredKey('_hd_auth_refresh_token') || error; error = this.checkStoredKey('_hd_auth_api_address') || error; error = this.checkStoredKey('_hd_auth_tenant') || error; error = this.checkStoredKey('_hd_auth_last_chosen_tenant_id') || error; error = this.checkStoredKey('_hd_auth_session_validation_ticket') || error; return !error; } checkStoredKey(key) { let val; this.storage.get(key, (v) => val = v); if (!val) { console.log('checkStorageConsistency ', key, ' empty'); return true; } return false; } 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.storage.set("_hd_signedin_tenants", ""); 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) { let was_remote = this.mode == Mode.Remote; //this.signout(); this.mode = m; if (m == Mode.Remote) { /*let org_api_addr :string; if(this.storage.get("_hd_auth_org_api_address", (v)=>{org_api_addr=v;})) { this.storage.set("_hd_auth_api_address", org_api_addr); this.storage.set("_hd_auth_org_api_address", ''); } */ } else { if (this.configuration && this.configuration.local_api) { //this.storage.set("_hd_auth_org_api_address", ''); //this.storage.set("_hd_auth_api_address", this.configuration.local_api); //this.setCurrentTenantAPI(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; } } export const session = writable(new Session(gv)); //# sourceMappingURL=Session.js.map