@humandialog/auth.svelte
Version:
Svelte package to deal with ObjectReef OAuth 2 Identity Provider
619 lines • 24.8 kB
JavaScript
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