This commit is contained in:
2024-09-29 13:56:38 +02:00
commit 31a6ef96d8
85 changed files with 13360 additions and 0 deletions
+362
View File
@@ -0,0 +1,362 @@
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 receptionTrUrl: string;
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.lointReception.data.IApp) {
this.receptionTrUrl = receptionBaseUrlArg
if (this.receptionTrUrl.endsWith('/')) {
this.receptionTrUrl = this.receptionTrUrl.slice(0, -1);
}
if (!this.receptionTrUrl.endsWith('/typedrequest')) {
this.receptionTrUrl = `${this.receptionTrUrl}/typedrequest`;
}
console.log(`reception client connecting to ${this.receptionTrUrl}`);
if (!appDataArg) {
appDataArg = {
id: '', // TODO
appUrl: `https://${window.location.host}/`,
description: null,
logoUrl: null,
name: null,
};
}
this.appData = appDataArg;
}
public requests = new IdpRequests(this);
/**
* 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.receptionTrUrl}/typedrequest`,
'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.receptionTrUrl}/typedrequest`,
'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.receptionTrUrl}/typedrequest`,
'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(
'https://sso.workspace.global/',
{
searchParams: {
appdata: plugins.smartjson.stringifyBase64(this.appData),
action: 'login',
},
}
);
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
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.receptionTrUrl}`)
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.receptionTrUrl}/`
);
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;
}
}
+62
View File
@@ -0,0 +1,62 @@
import * as plugins from './plugins.js';
import type { IdpClient } from "./classes.idpclient.js";
/**
* this class bundles all the typed requests that are used by the idp
*/
export class IdpRequests {
idpClientArg: IdpClient;
constructor(idpClientArg: IdpClient) {
this.idpClientArg = idpClientArg;
}
public get afterRegistrationEmailClicked () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_AfterRegistrationEmailClicked>(
this.idpClientArg.receptionTrUrl,
'afterRegistrationEmailClicked'
);
}
public get setData() {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_SetDataForRegistration>(
this.idpClientArg.receptionTrUrl,
'setDataForRegistration'
);
}
public get mobileNumberVerification () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_MobileVerificationForRegistration>(
this.idpClientArg.receptionTrUrl,
'mobileVerificationForRegistration'
);
}
public get finishRegistration() {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_FinishRegistration>(
this.idpClientArg.receptionTrUrl,
'finishRegistration'
);
}
public get loginWithUserNameAndPassword () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_LoginWithEmailOrUsernameAndPassword>(
this.idpClientArg.receptionTrUrl,
'loginWithEmailOrUsernameAndPassword'
);
}
public get obtainJwt () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
this.idpClientArg.receptionTrUrl,
'refreshJwt'
);
}
public get obtainOneTimeToken () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.idpClientArg.receptionTrUrl,
'exchangeRefreshTokenAndTransferToken'
);
}
}
+1
View File
@@ -0,0 +1 @@
export * from './classes.idpclient.js';
+26
View File
@@ -0,0 +1,26 @@
// losslessone_private scope
import * as lointReception from '../dist_ts_interfaces/index.js';
export { lointReception };
// apiglobal scope
import * as typedrequest from '@api.global/typedrequest';
import * as typedsocket from '@api.global/typedsocket';
export { typedrequest, typedsocket };
// pushrocks scope
import * as smartjson from '@push.rocks/smartjson';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as smarttime from '@push.rocks/smarttime';
import * as smarturl from '@push.rocks/smarturl';
import * as webjwt from '@push.rocks/webjwt';
import * as webstore from '@push.rocks/webstore';
export { smartjson, smartpromise, smartrx, smarttime, smarturl, webjwt, webstore };
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };