Files
app/ts_idpclient/classes.idpclient.ts
T

368 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2024-09-29 13:56:38 +02:00
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;
2024-09-29 13:56:38 +02:00
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.lointReception.data.IApp) {
if (receptionBaseUrlArg.endsWith('/')) {
receptionBaseUrlArg = receptionBaseUrlArg.slice(0, -1);
2024-09-29 13:56:38 +02:00
}
if (!receptionBaseUrlArg.endsWith('/typedrequest')) {
receptionBaseUrlArg = `${receptionBaseUrlArg}/typedrequest`;
2024-09-29 13:56:38 +02:00
}
this.parsedReceptionUrl = plugins.smarturl.Smarturl.createFromUrl(receptionBaseUrlArg);
console.log(`reception client connecting to ${this.parsedReceptionUrl.toString()}`);
2024-09-29 13:56:38 +02:00
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;
}
2024-09-29 13:56:38 +02:00
/**
* 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: 'idpglobalStore',
dbName: 'main',
2024-09-29 13:56:38 +02:00
});
public async storeJwt(jwtString: string) {
await this.ssoStore.set('idpJwt', jwtString);
2024-09-29 13:56:38 +02:00
}
public async getJwt(): Promise<string> {
return await this.ssoStore.get('idpJwt');
2024-09-29 13:56:38 +02:00
}
public async getJwtData(): Promise<plugins.lointReception.data.IJwt> {
return this.helpers.extractDataFromJwtString(await this.getJwt());
}
public async deleteJwt() {
await this.ssoStore.delete('idpJwt');
2024-09-29 13:56:38 +02:00
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(),
2024-09-29 13:56:38 +02:00
'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(),
2024-09-29 13:56:38 +02:00
'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(),
2024-09-29 13:56:38 +02:00
'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(),
2024-09-29 13:56:38 +02:00
{
searchParams: {
appdata: plugins.smartjson.stringifyBase64(this.appData),
},
}
);
if (!globalThis.location.href.startsWith(this.parsedReceptionUrl.toString())) {
2024-09-29 13:56:38 +02:00
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()}`);
2024-09-29 13:56:38 +02:00
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()
2024-09-29 13:56:38 +02:00
);
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() {
console.log('idpclient: getting roles and orgs...');
2024-09-29 13:56:38 +02:00
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;
}
}