feat(sdk): add initial browser and server authentication SDK exports
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
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() });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user