367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
import { IdpRequests } from './classes.idprequests.js';
|
|
import * as plugins from './plugins.js';
|
|
|
|
export class IdpClient {
|
|
// INSTANCE PRIVATE
|
|
private helpers = {
|
|
async extractDataFromJwtString(jwtString: string): Promise<plugins.lointReception.data.IJwt> {
|
|
return plugins.webjwt.getDataFromJwtString(jwtString);
|
|
},
|
|
};
|
|
|
|
// INSTANCE PUBLIC
|
|
|
|
public appData: plugins.lointReception.data.IApp;
|
|
public rolesReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
|
|
public organizationsReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
|
|
|
|
public parsedReceptionUrl: plugins.smarturl.Smarturl;
|
|
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.lointReception.data.IApp) {
|
|
if (receptionBaseUrlArg.endsWith('/')) {
|
|
receptionBaseUrlArg = receptionBaseUrlArg.slice(0, -1);
|
|
}
|
|
if (!receptionBaseUrlArg.endsWith('/typedrequest')) {
|
|
receptionBaseUrlArg = `${receptionBaseUrlArg}/typedrequest`;
|
|
}
|
|
this.parsedReceptionUrl = plugins.smarturl.Smarturl.createFromUrl(receptionBaseUrlArg);
|
|
console.log(`reception client connecting to ${this.parsedReceptionUrl.toString()}`);
|
|
if (!appDataArg) {
|
|
appDataArg = {
|
|
id: '', // TODO
|
|
appUrl: `https://${window.location.host}/`,
|
|
description: null,
|
|
logoUrl: null,
|
|
name: null,
|
|
};
|
|
}
|
|
this.appData = appDataArg;
|
|
}
|
|
|
|
public requests = new IdpRequests(this);
|
|
|
|
public checkWetherOnReceptionDomain() {
|
|
return plugins.smarturl.Smarturl.createFromUrl(window.location.href).hostname ===
|
|
this.parsedReceptionUrl.hostname;
|
|
}
|
|
|
|
/**
|
|
* app data can be transferred when redirecting to the sso domain using query params
|
|
* this message retrieves the app data when on the sso domain
|
|
*/
|
|
public async getAppDataOnSsoDomain() {
|
|
if (!window.location.href.startsWith('https://sso.workspace.global/')) {
|
|
console.error('You are trying to access SSO appData on a non sso domain.');
|
|
return null;
|
|
}
|
|
const appDataString = plugins.smarturl.Smarturl.createFromUrl(window.location.href).searchParams
|
|
.appdata;
|
|
if (!appDataString) {
|
|
console.error('no appdata query arg detected');
|
|
return null;
|
|
}
|
|
const appData = plugins.smartjson.parseBase64(appDataString);
|
|
return appData;
|
|
}
|
|
|
|
public async setJwt(jwtStringArg: string) {
|
|
await this.storeJwt(jwtStringArg);
|
|
}
|
|
|
|
/**
|
|
* a typedsocket for going reactive
|
|
*/
|
|
public typedsocket: plugins.typedsocket.TypedSocket;
|
|
|
|
/**
|
|
* a typed router to go reactive
|
|
*/
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
|
|
public statusObservable =
|
|
new plugins.smartrx.rxjs.Subject<plugins.lointReception.data.TLoginStatus>();
|
|
|
|
public ssoStore = new plugins.webstore.WebStore({
|
|
storeName: 'wgsso',
|
|
dbName: 'wgsso',
|
|
});
|
|
|
|
public async storeJwt(jwtString: string) {
|
|
await this.ssoStore.set('wgJwt', jwtString);
|
|
}
|
|
|
|
public async getJwt(): Promise<string> {
|
|
return await this.ssoStore.get('wgJwt');
|
|
}
|
|
public async getJwtData(): Promise<plugins.lointReception.data.IJwt> {
|
|
return this.helpers.extractDataFromJwtString(await this.getJwt());
|
|
}
|
|
|
|
public async deleteJwt() {
|
|
await this.ssoStore.delete('wgJwt');
|
|
console.log('removed jwt');
|
|
}
|
|
|
|
/**
|
|
* performs jwt housekeeping
|
|
* only call if jwt is present
|
|
* @returns
|
|
*/
|
|
public async performJwtHousekeeping() {
|
|
let jwt = await this.getJwt();
|
|
if (!jwt) {
|
|
return null;
|
|
}
|
|
const extractedJwt = await this.helpers.extractDataFromJwtString(jwt);
|
|
if (extractedJwt.data.refreshFrom < Date.now() && Date.now() < extractedJwt.data.validUntil) {
|
|
jwt = await this.refreshJwt();
|
|
} else if (Date.now() > extractedJwt.data.validUntil) {
|
|
this.deleteJwt();
|
|
}
|
|
return jwt;
|
|
}
|
|
|
|
public async refreshJwt(refreshTokenArg?: string): Promise<string> {
|
|
let extractedJwt: plugins.lointReception.data.IJwt;
|
|
|
|
if (!refreshTokenArg) {
|
|
extractedJwt = await this.helpers.extractDataFromJwtString(await this.getJwt());
|
|
}
|
|
const refreshJwtReq =
|
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
|
|
this.parsedReceptionUrl.toString(),
|
|
'refreshJwt'
|
|
);
|
|
const response = await refreshJwtReq.fire({
|
|
refreshToken: refreshTokenArg || extractedJwt.data.refreshToken,
|
|
});
|
|
if (response.jwt) {
|
|
await this.storeJwt(response.jwt);
|
|
} else {
|
|
await this.deleteJwt();
|
|
}
|
|
this.statusObservable.next(response.status);
|
|
return await this.getJwt();
|
|
}
|
|
|
|
/**
|
|
* can be used to switch between pages
|
|
*/
|
|
public async getTransferToken(appDataArg?: plugins.lointReception.data.IApp): Promise<string> {
|
|
const jwt = await this.performJwtHousekeeping();
|
|
const extractedJwt = await this.helpers.extractDataFromJwtString(jwt);
|
|
const getTransferToken =
|
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
|
this.parsedReceptionUrl.toString(),
|
|
'exchangeRefreshTokenAndTransferToken'
|
|
);
|
|
const response = await getTransferToken.fire({
|
|
refreshToken: extractedJwt.data.refreshToken,
|
|
appData: appDataArg || this.appData,
|
|
});
|
|
return response.transferToken;
|
|
}
|
|
|
|
/**
|
|
* gets a transfer token and switches to a location
|
|
*/
|
|
public async getTransferTokenAndSwitchToLocation(newLocationArg: string): Promise<void> {
|
|
const transferToken = await this.getTransferToken();
|
|
if (!transferToken) {
|
|
alert('failed to get transfer token!');
|
|
}
|
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl(newLocationArg, {
|
|
searchParams: {
|
|
transfertoken: transferToken,
|
|
},
|
|
});
|
|
const transferUrl = urlInstance.toString();
|
|
window.location.href = transferUrl;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* processes a transfer token
|
|
*/
|
|
public async processTransferToken(): Promise<boolean> {
|
|
const href = window.location.href;
|
|
const url = plugins.smarturl.Smarturl.createFromUrl(href);
|
|
const transferToken = url.searchParams['transfertoken'];
|
|
if (transferToken) {
|
|
const getTransferToken =
|
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
|
this.parsedReceptionUrl.toString(),
|
|
'exchangeRefreshTokenAndTransferToken'
|
|
);
|
|
const response = await getTransferToken.fire({
|
|
transferToken,
|
|
appData: this.appData,
|
|
});
|
|
if (response.refreshToken) {
|
|
await this.refreshJwt(response.refreshToken);
|
|
} else {
|
|
globalThis.alert?.('transfer token invalid');
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Login Status stuff
|
|
public async checkJwtPresent() {
|
|
const jwt = await this.performJwtHousekeeping();
|
|
if (jwt) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* forces the current user to login
|
|
* @param requireLoginArg
|
|
* @returns
|
|
*/
|
|
public async determineLoginStatus(requireLoginArg: boolean = false): Promise<boolean> {
|
|
const jwtPresent = await this.checkJwtPresent();
|
|
if (jwtPresent) {
|
|
const jwt = await this.performJwtHousekeeping();
|
|
return !!jwt;
|
|
} else {
|
|
const transferTokenResult = await this.processTransferToken();
|
|
if (transferTokenResult) {
|
|
// we are in the clear
|
|
return true;
|
|
} else {
|
|
if (requireLoginArg) {
|
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl(
|
|
this.parsedReceptionUrl.clone().set('path', '/login').toString(),
|
|
{
|
|
searchParams: {
|
|
appdata: plugins.smartjson.stringifyBase64(this.appData),
|
|
},
|
|
}
|
|
);
|
|
if (!globalThis.location.href.startsWith(this.parsedReceptionUrl.toString())) {
|
|
globalThis.location.href = urlInstance.toString();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* logs out the current user
|
|
*/
|
|
public async logout() {
|
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl('https://sso.workspace.global/', {
|
|
searchParams: {
|
|
appdata: plugins.smartjson.stringifyBase64(this.appData),
|
|
action: 'logout',
|
|
},
|
|
});
|
|
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
|
|
// we are somewhere in an app
|
|
await this.deleteJwt();
|
|
globalThis.location.href = urlInstance.toString();
|
|
} else {
|
|
// we are in the sso page
|
|
await this.enableTypedSocket();
|
|
console.log(`logging out against ${this.parsedReceptionUrl.toString()}`);
|
|
const logoutTr =
|
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.ILogoutRequest>(
|
|
'logout'
|
|
);
|
|
await logoutTr.fire({
|
|
refreshToken: (await this.getJwtData()).data.refreshToken,
|
|
});
|
|
await this.deleteJwt();
|
|
const appData = await this.getAppDataOnSsoDomain();
|
|
if (appData) {
|
|
console.log(`redirecting to app after logout: ${appData.appUrl}`);
|
|
window.location.href = appData.appUrl;
|
|
} else {
|
|
console.error('no appData provided. Not redirecting after logout.');
|
|
}
|
|
}
|
|
}
|
|
|
|
public typedsocketDeferred = plugins.smartpromise.defer();
|
|
public async enableTypedSocket() {
|
|
if (this.typedsocketDeferred.claimed) {
|
|
return this.typedsocketDeferred.promise;
|
|
}
|
|
this.typedsocketDeferred.claim();
|
|
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(
|
|
this.typedrouter,
|
|
this.parsedReceptionUrl.toString()
|
|
);
|
|
this.typedsocketDeferred.resolve(this.typedsocket);
|
|
return this.typedsocketDeferred.promise;
|
|
}
|
|
|
|
public async stop() {
|
|
await this.typedsocket?.stop();
|
|
}
|
|
|
|
// ==================================
|
|
// Organization and Settings stuff
|
|
// ==================================
|
|
public async createOrganization(
|
|
orgNameArg: string,
|
|
orgSlugArg: string,
|
|
modeArg: 'checkAvailability' | 'manifest'
|
|
) {
|
|
await this.typedsocketDeferred.promise;
|
|
const validateOrg =
|
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_CreateOrganization>(
|
|
'createOrganization'
|
|
);
|
|
const response = await validateOrg.fire({
|
|
jwt: await this.getJwt(),
|
|
action: modeArg,
|
|
organizationName: orgNameArg,
|
|
organizationSlug: orgSlugArg,
|
|
userId: (await this.getJwtData()).id,
|
|
});
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* gets the current OrganizationRoles
|
|
*/
|
|
public async getRolesAndOrganizations() {
|
|
await this.typedsocketDeferred.promise;
|
|
const rolesAndOrganizationsForUserId =
|
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_GetRolesAndOrganizationsForUserId>(
|
|
'getRolesAndOrganizationsForUserId'
|
|
);
|
|
const response = await rolesAndOrganizationsForUserId.fire({
|
|
jwt: await this.getJwt(),
|
|
userId: (await this.getJwtData()).id,
|
|
});
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* updates the PaddleCheckoutId for an organization.
|
|
*/
|
|
public async updatePaddleCheckoutId(orgIdArg: string, checkoutIdArg: string) {
|
|
await this.typedsocketDeferred.promise;
|
|
const updateBillingPlan =
|
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_UpdatePaymentMethod>(
|
|
'updatePaymentMethod'
|
|
);
|
|
const response = await updateBillingPlan.fire({
|
|
jwtString: await this.getJwt(),
|
|
orgId: orgIdArg,
|
|
paddle: {
|
|
checkoutId: checkoutIdArg,
|
|
},
|
|
});
|
|
return response;
|
|
}
|
|
}
|