319 lines
11 KiB
TypeScript
319 lines
11 KiB
TypeScript
|
|
import { IdpRequests } from './classes.idprequests.js';
|
||
|
|
import * as plugins from './plugins.js';
|
||
|
|
|
||
|
|
export class IdpClient {
|
||
|
|
private helpers = {
|
||
|
|
async extractDataFromJwtString(jwtString: string): Promise<plugins.idpInterfaces.data.IJwt> {
|
||
|
|
return plugins.webjwt.getDataFromJwtString(jwtString);
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
public appData: plugins.idpInterfaces.data.IAppLegacy;
|
||
|
|
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.idpInterfaces.data.IAppLegacy) {
|
||
|
|
if (receptionBaseUrlArg.endsWith('/')) {
|
||
|
|
receptionBaseUrlArg = receptionBaseUrlArg.slice(0, -1);
|
||
|
|
}
|
||
|
|
if (!receptionBaseUrlArg.endsWith('/typedrequest')) {
|
||
|
|
receptionBaseUrlArg = `${receptionBaseUrlArg}/typedrequest`;
|
||
|
|
}
|
||
|
|
this.parsedReceptionUrl = plugins.smarturl.Smarturl.createFromUrl(receptionBaseUrlArg);
|
||
|
|
if (!appDataArg) {
|
||
|
|
appDataArg = {
|
||
|
|
id: '',
|
||
|
|
appUrl: typeof window !== 'undefined' ? `https://${window.location.host}/` : '',
|
||
|
|
description: '',
|
||
|
|
logoUrl: '',
|
||
|
|
name: '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
this.appData = appDataArg;
|
||
|
|
}
|
||
|
|
|
||
|
|
public requests = new IdpRequests(this);
|
||
|
|
|
||
|
|
public checkWetherOnReceptionDomain() {
|
||
|
|
return plugins.smarturl.Smarturl.createFromUrl(window.location.href).hostname ===
|
||
|
|
this.parsedReceptionUrl.hostname;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
return plugins.smartjson.parseBase64(appDataString);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async setJwt(jwtStringArg: string) {
|
||
|
|
await this.storeJwt(jwtStringArg);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async setRefreshToken(refreshTokenArg: string) {
|
||
|
|
await this.storeRefreshToken(refreshTokenArg);
|
||
|
|
}
|
||
|
|
|
||
|
|
public typedsocket!: plugins.typedsocket.TypedSocket;
|
||
|
|
public typedrouter: any = new plugins.typedrequest.TypedRouter();
|
||
|
|
public statusObservable = new plugins.smartrx.rxjs.Subject<plugins.idpInterfaces.data.TLoginStatus>();
|
||
|
|
|
||
|
|
public ssoStore = new plugins.webstore.WebStore({
|
||
|
|
storeName: 'idpglobalStore',
|
||
|
|
dbName: 'main',
|
||
|
|
});
|
||
|
|
|
||
|
|
public async storeJwt(jwtString: string) {
|
||
|
|
await this.ssoStore.set('idpJwt', jwtString);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async storeRefreshToken(refreshToken: string) {
|
||
|
|
await this.ssoStore.set('idpRefreshToken', refreshToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async getJwt(): Promise<string> {
|
||
|
|
return await this.ssoStore.get('idpJwt');
|
||
|
|
}
|
||
|
|
|
||
|
|
public async getRefreshToken(): Promise<string> {
|
||
|
|
return await this.ssoStore.get('idpRefreshToken');
|
||
|
|
}
|
||
|
|
|
||
|
|
public async getJwtData(): Promise<plugins.idpInterfaces.data.IJwt> {
|
||
|
|
return this.helpers.extractDataFromJwtString(await this.getJwt());
|
||
|
|
}
|
||
|
|
|
||
|
|
public async deleteJwt() {
|
||
|
|
await this.ssoStore.delete('idpJwt');
|
||
|
|
}
|
||
|
|
|
||
|
|
public async deleteRefreshToken() {
|
||
|
|
await this.ssoStore.delete('idpRefreshToken');
|
||
|
|
}
|
||
|
|
|
||
|
|
public async clearAuthState() {
|
||
|
|
await Promise.all([this.deleteJwt(), this.deleteRefreshToken()]);
|
||
|
|
}
|
||
|
|
|
||
|
|
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) {
|
||
|
|
await this.deleteJwt();
|
||
|
|
jwt = await this.refreshJwt();
|
||
|
|
}
|
||
|
|
return jwt;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async refreshJwt(refreshTokenArg?: string): Promise<string | null> {
|
||
|
|
const refreshToken = refreshTokenArg || (await this.getRefreshToken());
|
||
|
|
|
||
|
|
if (!refreshToken) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const refreshJwtReq = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>('refreshJwt');
|
||
|
|
const response = await refreshJwtReq
|
||
|
|
.fire({ refreshToken })
|
||
|
|
.catch(async () => {
|
||
|
|
await this.clearAuthState();
|
||
|
|
return null;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response?.jwt) {
|
||
|
|
await this.clearAuthState();
|
||
|
|
this.statusObservable.next(response?.status || 'loggedOut');
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (response.refreshToken) {
|
||
|
|
await this.storeRefreshToken(response.refreshToken);
|
||
|
|
}
|
||
|
|
await this.storeJwt(response.jwt);
|
||
|
|
this.statusObservable.next(response.status);
|
||
|
|
return response.jwt;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async getTransferToken(appDataArg?: plugins.idpInterfaces.data.IAppLegacy): Promise<string | null> {
|
||
|
|
await this.performJwtHousekeeping();
|
||
|
|
const refreshToken = await this.getRefreshToken();
|
||
|
|
if (!refreshToken) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const getTransferToken = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>('exchangeRefreshTokenAndTransferToken');
|
||
|
|
const response = await getTransferToken.fire({
|
||
|
|
refreshToken,
|
||
|
|
appData: appDataArg || this.appData,
|
||
|
|
});
|
||
|
|
return response.transferToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 },
|
||
|
|
});
|
||
|
|
window.location.href = urlInstance.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async processTransferToken(): Promise<boolean> {
|
||
|
|
const href = window.location.href;
|
||
|
|
const url = plugins.smarturl.Smarturl.createFromUrl(href);
|
||
|
|
const transferToken = url.searchParams['transfertoken'];
|
||
|
|
if (transferToken) {
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const getTransferToken = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>('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;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async checkJwtPresent() {
|
||
|
|
const jwt = await this.performJwtHousekeeping();
|
||
|
|
return !!jwt;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async determineLoginStatus(requireLoginArg: boolean = false): Promise<boolean> {
|
||
|
|
const jwtPresent = await this.checkJwtPresent();
|
||
|
|
if (jwtPresent) {
|
||
|
|
const jwt = await this.performJwtHousekeeping();
|
||
|
|
return !!jwt;
|
||
|
|
}
|
||
|
|
const refreshToken = await this.getRefreshToken();
|
||
|
|
if (refreshToken) {
|
||
|
|
const jwt = await this.refreshJwt(refreshToken);
|
||
|
|
if (jwt) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const transferTokenResult = await this.processTransferToken();
|
||
|
|
if (transferTokenResult) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async logout() {
|
||
|
|
const idpLogoutUrl = this.parsedReceptionUrl.clone().set('path', '/logout');
|
||
|
|
const refreshToken = await this.getRefreshToken();
|
||
|
|
if (!globalThis.location.href.startsWith(idpLogoutUrl.origin)) {
|
||
|
|
await this.clearAuthState();
|
||
|
|
globalThis.location.href = idpLogoutUrl.toString();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!refreshToken) {
|
||
|
|
await this.clearAuthState();
|
||
|
|
window.location.href = this.parsedReceptionUrl.origin;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
await this.enableTypedSocket();
|
||
|
|
const logoutTr = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.ILogoutRequest>('logout');
|
||
|
|
await logoutTr.fire({ refreshToken });
|
||
|
|
await this.clearAuthState();
|
||
|
|
const appData = await this.getAppDataOnSsoDomain();
|
||
|
|
if (appData) {
|
||
|
|
window.location.href = appData.appUrl;
|
||
|
|
} else if (window.location.href.startsWith(idpLogoutUrl.origin)) {
|
||
|
|
window.location.href = this.parsedReceptionUrl.origin;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public typedsocketDeferred = plugins.smartpromise.defer<plugins.typedsocket.TypedSocket>();
|
||
|
|
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async createOrganization(orgNameArg: string, orgSlugArg: string, modeArg: 'checkAvailability' | 'manifest') {
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const validateOrg = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CreateOrganization>('createOrganization');
|
||
|
|
return validateOrg.fire({
|
||
|
|
jwt: await this.getJwt(),
|
||
|
|
action: modeArg,
|
||
|
|
organizationName: orgNameArg,
|
||
|
|
organizationSlug: orgSlugArg,
|
||
|
|
userId: (await this.getJwtData()).id,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public async getRolesAndOrganizations() {
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const rolesAndOrganizationsForUserId = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetRolesAndOrganizationsForUserId>('getRolesAndOrganizationsForUserId');
|
||
|
|
return rolesAndOrganizationsForUserId.fire({
|
||
|
|
jwt: await this.getJwt(),
|
||
|
|
userId: (await this.getJwtData()).id,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public async updatePaddleCheckoutId(orgIdArg: string, checkoutIdArg: string) {
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const updateBillingPlan = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpdatePaymentMethod>('updatePaymentMethod');
|
||
|
|
return updateBillingPlan.fire({
|
||
|
|
jwtString: await this.getJwt(),
|
||
|
|
orgId: orgIdArg,
|
||
|
|
paddle: { checkoutId: checkoutIdArg },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public async whoIs() {
|
||
|
|
await this.typedsocketDeferred.promise;
|
||
|
|
const whoIs = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_WhoIs>('whoIs');
|
||
|
|
return whoIs.fire({ jwt: await this.getJwt() });
|
||
|
|
}
|
||
|
|
}
|