@humandialog/auth.svelte
Version:
Svelte package to deal with ObjectReef OAuth 2 Identity Provider
500 lines (433 loc) • 17.2 kB
text/typescript
import { session, Session, App_instance_info } from "./Session";
import type { Configuration, Local_user } from "./Configuration";
import { derived, readable, get } from "svelte/store";
import { gv } from "./Storage";
const s = session;
let refreshing = false;
export class reef {
public static configure(cfg) {
let _session: Session = get(session);
_session.configure(cfg);
}
public static async fetch(...args) {
let [resource, options] = args;
let given_url = '';
given_url = resource;
let _session: Session = get(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<number>("iat")
const exp = _session.accessToken.get_claim<number>("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: Local_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);
}
private static correct_path_with_api_version_if_needed(path) {
if (path.startsWith('/json/'))
return path;
let apiver = 'v001'; // default
let _session: Session = get(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}`;
}
public 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;
}
}
public 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;
}
}
catch (err) {
console.error(err);
if(onError)
onError(err)
return null;
}
}
public static async delete(_path, onError) {
let path = reef.correct_path_with_api_version_if_needed(_path)
try {
let res = await reef.fetch(path, { method: 'DELETE' });
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;
}
}
public static async refreshTokens(_session: Session|null = null): Promise<boolean> {
if(!_session)
_session = get(session);
console.log('refreshTokens')
if (_session.refreshToken == null)
{
console.log('refreshToken is null')
return false;
}
let refresh_token: string = _session.refreshToken.raw;
if (refresh_token == "")
{
console.log('refreshToken is empty')
return false;
}
let conf: Configuration = _session.configuration;
let data = new URLSearchParams();
data.append("grant_type", "refresh_token");
data.append("refresh_token", refresh_token);
data.append("client_id", conf.client_id);
data.append("scope", conf.scope);
if(conf.tenant)
data.append("tenant", conf.tenant);
if(conf.groups_only)
data.append("groups_only", "true");
try {
const res = await fetch(conf.iss + "/auth/token",
{
method: 'post',
headers: new Headers({
'Authorization': 'Basic ' + btoa('' + conf.client_id + ':' + conf.client_secret),
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}),
body: data,
credentials: "include"
});
if (res.ok) {
console.log('/auth/token 200 OK')
let tokens = await res.json();
if (tokens.tenants && Array.isArray(tokens.tenants) && tokens.tenants.length > 1)
{
if(conf.tenant) // do we have global tenant specified?
{
let filteredTenants = []
if(conf.groups_only)
filteredTenants = tokens.tenants.filter(t => t.id.startsWith(conf.tenant + '/'))
else
filteredTenants = tokens.tenants.filter(t => t.id.startsWith(conf.tenant))
tokens.tenants = [...filteredTenants];
if( tokens.tenants.length == 1)
{
if (_session.refreshTokens(tokens))
return true;
else
{
console.log("Can't signin (1)", tokens)
return false;
}
}
}
const lastChosenTenantId = _session.lastChosenTenantId;
if(lastChosenTenantId)
{
if(tokens.tenants.some(t => t.id == lastChosenTenantId)) // is last used included
{
if(_session.refreshTokens(tokens, lastChosenTenantId))
{
return true;
}
else
{
console.log("Can't signin (2)", tokens)
return false;
}
}
else
{
console.log("Can't signin (3)", lastChosenTenantId)
return false;
}
}
else
{
console.log("Can't signin (4)")
return false;
}
}
if (_session.refreshTokens(tokens))
return true;
else
{
console.log("Can't signin (5)", tokens)
return false;
}
}
else {
_session.signout(); // clean up session data
let err = await res.json();
console.error(err.error, err.error_description);
return false;
}
}
catch (error) {
_session.signout(); // clean up session data
console.error(error);
return false;
}
}
public static async amIAdmin(): Promise<boolean> {
let _session: Session = get(session);
let tenant_id = _session.tid;
let path = `/auth/am_i_admin?tenant=${tenant_id}`;
try {
let res = await reef.fetch(path, {})
if (res.ok) {
const response_string = await res.text();
if (!response_string)
return false
else {
let res = JSON.parse(response_string);
return res.response ?? false;
}
}
else
return false;
}
catch (err) {
console.error(err);
return false;
}
}
public static redirectToSignIn() {
console.log('redirectToSignIn')
let current_path: string;
current_path = window.location.href;
let navto: string = window.location.pathname;
if (!navto)
navto = '/';
if (!navto.endsWith('/'))
navto += '/';
navto += "#/auth/signin?redirect=" + encodeURIComponent(current_path);
//await tick();
window.location.href = navto;
}
public static async getAppInstanceInfo(onError) : Promise<App_instance_info>
{
let _session: Session = get(session);
if(_session.appInstanceInfo)
return _session.appInstanceInfo;
if(!_session.configuration.tenant)
return null;
let app_id = _session.appId
if(!app_id)
return null;
try
{
const res = await reef.fetch(`/dev/get-tenant-info?app_id=${app_id}&tenant_id=${_session.configuration.tenant}`, {})
if(res.ok)
{
let response = await res.json();
_session.appInstanceInfo = response;
return response;
}
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;
}
}
public static locationChanged(...args) {
if (get(loc).href != window.location.href) {
let event = new PopStateEvent('popstate', { state: {} });
dispatchEvent(event);
}
}
}
function get_location() {
const href = window.location.href;
const hashPosition = href.indexOf('#/')
let base_address = window.location.pathname;
let location = (hashPosition > -1) ? href.substr(hashPosition + 1) : '/'
const orgin = window.location.origin
// Check if there's a querystring
const qsPosition = location.indexOf('?')
let querystring = ''
if (qsPosition > -1) {
querystring = location.substr(qsPosition + 1)
location = location.substr(0, qsPosition)
}
return { href, location, querystring, base_address, orgin }
}
const loc = readable(
null,
function start(set) {
set(get_location())
const update = () => { set(get_location()) }
window.addEventListener('hashchange', update, false); // hash based routers
window.addEventListener('popstate', (event) => { update(); }); // history based routers
return function stop() {
window.removeEventListener('hashchange', update, false);
window.removeEventListener('popstate', (event) => { update(); });
}
}
)
export const _hd_auth_location = derived(loc, ($loc) => $loc.location);
export const _hd_auth_querystring = derived(loc, ($loc) => $loc.querystring);
export const _hd_auth_base_address = derived(loc, ($loc) => $loc.base_address);
export const signInHRef = derived(loc, ($loc) => '#/auth/signin?redirect=' + encodeURIComponent($loc.href));
export const signOutHRef = derived(loc, ($loc) => '#/auth/signout?redirect=' + encodeURIComponent($loc.orgin));
export const signUpHRef = derived(loc, ($loc) => '#/auth/signup?redirect=' + encodeURIComponent($loc.href));