@idp.global/sdk 5.0.1 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +20 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_browser/classes.idpclient.d.ts +4 -0
- package/dist_ts_browser/classes.idpclient.js +71 -19
- package/dist_ts_browser/classes.idprequests.d.ts +23 -6
- package/dist_ts_browser/classes.idprequests.js +70 -19
- package/dist_ts_server/classes.idp-global-server-client.d.ts +18 -0
- package/dist_ts_server/classes.idp-global-server-client.js +154 -11
- package/package.json +3 -3
- package/readme.md +29 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_browser/classes.idpclient.ts +73 -20
- package/ts_browser/classes.idprequests.ts +90 -22
- package/ts_server/classes.idp-global-server-client.ts +181 -14
|
@@ -2,6 +2,7 @@ import * as plugins from './plugins.js';
|
|
|
2
2
|
|
|
3
3
|
export interface IIdpGlobalServerClientOptions {
|
|
4
4
|
baseUrl?: string;
|
|
5
|
+
backendToken?: string;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export const defaultIdpGlobalBaseUrl = 'https://idp.global';
|
|
@@ -27,35 +28,158 @@ export class IdpMfaRequiredError extends Error {
|
|
|
27
28
|
export class IdpGlobalServerClient {
|
|
28
29
|
private typedrouter: any = new plugins.typedrequest.TypedRouter();
|
|
29
30
|
private typedsocket?: plugins.typedsocket.TypedSocket;
|
|
30
|
-
private typedsocketDeferred =
|
|
31
|
+
private typedsocketDeferred = this.createConnectionDeferred();
|
|
32
|
+
private publicKeyPushHandlers: Array<(publicKeyPemArg: string) => void | Promise<void>> = [];
|
|
33
|
+
private blocklistPushHandlers: Array<(blockedJwtIdsArg: string[]) => void | Promise<void>> = [];
|
|
34
|
+
private connectionGeneration = 0;
|
|
35
|
+
private pendingSocketPromise?: Promise<plugins.typedsocket.TypedSocket>;
|
|
36
|
+
private connectionAbortController?: AbortController;
|
|
37
|
+
private stopWaiters = new Set<(errorArg: Error) => void>();
|
|
38
|
+
private stopping = false;
|
|
31
39
|
|
|
32
|
-
constructor(private optionsArg: IIdpGlobalServerClientOptions = {}) {
|
|
40
|
+
constructor(private optionsArg: IIdpGlobalServerClientOptions = {}) {
|
|
41
|
+
this.typedrouter.addTypedHandler(
|
|
42
|
+
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_PushPublicKeyForValidation>(
|
|
43
|
+
'pushPublicKeyForValidation',
|
|
44
|
+
async (requestArg) => {
|
|
45
|
+
await Promise.all(this.publicKeyPushHandlers.map((handlerArg) => handlerArg(requestArg.publicKeyPem)));
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
this.typedrouter.addTypedHandler(
|
|
51
|
+
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_PushOrGetJwtIdBlocklist>(
|
|
52
|
+
'pushOrGetJwtIdBlocklist',
|
|
53
|
+
async (requestArg) => {
|
|
54
|
+
if (requestArg.blockedJwtIds) {
|
|
55
|
+
await Promise.all(this.blocklistPushHandlers.map((handlerArg) => handlerArg(requestArg.blockedJwtIds!)));
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
33
62
|
|
|
34
63
|
public async connect(): Promise<plugins.typedsocket.TypedSocket> {
|
|
64
|
+
if (this.stopping) {
|
|
65
|
+
throw new Error('IdpGlobalServerClient is stopping');
|
|
66
|
+
}
|
|
35
67
|
if (this.typedsocketDeferred.claimed) {
|
|
36
68
|
return this.typedsocketDeferred.promise;
|
|
37
69
|
}
|
|
38
|
-
this.typedsocketDeferred
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
const deferred = this.typedsocketDeferred;
|
|
71
|
+
const generation = this.connectionGeneration;
|
|
72
|
+
deferred.claim();
|
|
73
|
+
let createdSocket: plugins.typedsocket.TypedSocket | undefined;
|
|
74
|
+
let socketPromise: Promise<plugins.typedsocket.TypedSocket> | undefined;
|
|
75
|
+
const connectionAbortController = new AbortController();
|
|
76
|
+
try {
|
|
77
|
+
this.connectionAbortController = connectionAbortController;
|
|
78
|
+
socketPromise = plugins.typedsocket.TypedSocket.createClient(
|
|
79
|
+
this.typedrouter,
|
|
80
|
+
this.getTypedRequestUrl(),
|
|
81
|
+
{ autoReconnect: true, abortSignal: connectionAbortController.signal },
|
|
82
|
+
);
|
|
83
|
+
this.pendingSocketPromise = socketPromise;
|
|
84
|
+
createdSocket = await this.raceWithStop(socketPromise);
|
|
85
|
+
if (generation !== this.connectionGeneration) {
|
|
86
|
+
await createdSocket.stop();
|
|
87
|
+
if (deferred.status === 'pending') {
|
|
88
|
+
deferred.reject(new Error('IdpGlobalServerClient stopped while connecting'));
|
|
89
|
+
}
|
|
90
|
+
return deferred.promise;
|
|
91
|
+
}
|
|
92
|
+
this.typedsocket = createdSocket;
|
|
93
|
+
deferred.resolve(createdSocket);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (generation === this.connectionGeneration && (!createdSocket || this.typedsocket === createdSocket)) {
|
|
96
|
+
this.typedsocket = undefined;
|
|
97
|
+
}
|
|
98
|
+
await createdSocket?.stop().catch(() => undefined);
|
|
99
|
+
if (deferred.status === 'pending') {
|
|
100
|
+
deferred.reject(error);
|
|
101
|
+
}
|
|
102
|
+
if (this.typedsocketDeferred === deferred) {
|
|
103
|
+
this.typedsocketDeferred = this.createConnectionDeferred();
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
} finally {
|
|
107
|
+
if (socketPromise && this.pendingSocketPromise === socketPromise) {
|
|
108
|
+
this.pendingSocketPromise = undefined;
|
|
109
|
+
}
|
|
110
|
+
if (this.connectionAbortController === connectionAbortController) {
|
|
111
|
+
this.connectionAbortController = undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return deferred.promise;
|
|
45
115
|
}
|
|
46
116
|
|
|
47
117
|
public async stop(): Promise<void> {
|
|
48
|
-
|
|
118
|
+
this.stopping = true;
|
|
119
|
+
this.connectionGeneration++;
|
|
120
|
+
const stopError = new Error('IdpGlobalServerClient stopped');
|
|
121
|
+
this.rejectStopWaiters(stopError);
|
|
122
|
+
const socket = this.typedsocket;
|
|
123
|
+
const pendingSocketPromise = this.pendingSocketPromise;
|
|
124
|
+
this.connectionAbortController?.abort();
|
|
125
|
+
this.connectionAbortController = undefined;
|
|
49
126
|
this.typedsocket = undefined;
|
|
127
|
+
if (this.typedsocketDeferred.status === 'pending' && this.typedsocketDeferred.claimed) {
|
|
128
|
+
this.typedsocketDeferred.reject(stopError);
|
|
129
|
+
}
|
|
130
|
+
await socket?.stop();
|
|
131
|
+
const pendingSocket = await pendingSocketPromise?.catch(() => undefined);
|
|
132
|
+
if (pendingSocket && pendingSocket !== socket) {
|
|
133
|
+
await pendingSocket.stop().catch(() => undefined);
|
|
134
|
+
}
|
|
135
|
+
this.typedsocketDeferred = this.createConnectionDeferred();
|
|
136
|
+
this.pendingSocketPromise = undefined;
|
|
137
|
+
this.stopping = false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public onPublicKeyPush(handlerArg: (publicKeyPemArg: string) => void | Promise<void>): () => void {
|
|
141
|
+
this.publicKeyPushHandlers.push(handlerArg);
|
|
142
|
+
return () => {
|
|
143
|
+
this.publicKeyPushHandlers = this.publicKeyPushHandlers.filter((handler) => handler !== handlerArg);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public onBlocklistPush(handlerArg: (blockedJwtIdsArg: string[]) => void | Promise<void>): () => void {
|
|
148
|
+
this.blocklistPushHandlers.push(handlerArg);
|
|
149
|
+
return () => {
|
|
150
|
+
this.blocklistPushHandlers = this.blocklistPushHandlers.filter((handler) => handler !== handlerArg);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async registerBackendService(backendTokenArg = this.optionsArg.backendToken): Promise<void> {
|
|
155
|
+
const backendToken = this.requireBackendToken(backendTokenArg);
|
|
156
|
+
const socket = await this.connect();
|
|
157
|
+
await this.applyBackendServiceTag(socket, backendToken);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public async getPublicKeyForValidation(backendTokenArg = this.optionsArg.backendToken): Promise<string> {
|
|
161
|
+
const backendToken = this.requireBackendToken(backendTokenArg);
|
|
162
|
+
const socket = await this.connect();
|
|
163
|
+
const request = socket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetPublicKeyForValidation>('getPublicKeyForValidation');
|
|
164
|
+
const response = await this.raceWithStop(request.fire({ backendToken }));
|
|
165
|
+
return response.publicKeyPem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public async getJwtIdBlocklist(backendTokenArg = this.optionsArg.backendToken): Promise<string[]> {
|
|
169
|
+
const backendToken = this.requireBackendToken(backendTokenArg);
|
|
170
|
+
const socket = await this.connect();
|
|
171
|
+
const request = socket.createTypedRequest<plugins.idpInterfaces.request.IReq_PushOrGetJwtIdBlocklist>('pushOrGetJwtIdBlocklist');
|
|
172
|
+
const response = await this.raceWithStop(request.fire({ backendToken }));
|
|
173
|
+
return response.blockedJwtIds || [];
|
|
50
174
|
}
|
|
51
175
|
|
|
52
176
|
public async loginWithEmailAndPassword(optionsArg: { email: string; password: string }): Promise<IIdpPasswordAuthResult> {
|
|
53
177
|
const socket = await this.connect();
|
|
54
178
|
const loginRequest = socket.createTypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>('loginWithEmailOrUsernameAndPassword');
|
|
55
|
-
const loginResponse = await loginRequest.fire({
|
|
179
|
+
const loginResponse = await this.raceWithStop(loginRequest.fire({
|
|
56
180
|
username: optionsArg.email,
|
|
57
181
|
password: optionsArg.password,
|
|
58
|
-
});
|
|
182
|
+
}));
|
|
59
183
|
if (loginResponse.twoFaNeeded) {
|
|
60
184
|
throw new IdpMfaRequiredError({
|
|
61
185
|
mfaChallengeToken: (loginResponse as any).mfaChallengeToken,
|
|
@@ -67,13 +191,13 @@ export class IdpGlobalServerClient {
|
|
|
67
191
|
}
|
|
68
192
|
|
|
69
193
|
const refreshRequest = socket.createTypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>('refreshJwt');
|
|
70
|
-
const refreshResponse = await refreshRequest.fire({ refreshToken: loginResponse.refreshToken });
|
|
194
|
+
const refreshResponse = await this.raceWithStop(refreshRequest.fire({ refreshToken: loginResponse.refreshToken }));
|
|
71
195
|
if (!refreshResponse.jwt) {
|
|
72
196
|
throw new Error('IdP did not return a JWT');
|
|
73
197
|
}
|
|
74
198
|
|
|
75
199
|
const whoIsRequest = socket.createTypedRequest<plugins.idpInterfaces.request.IReq_WhoIs>('whoIs');
|
|
76
|
-
const whoIsResponse = await whoIsRequest.fire({ jwt: refreshResponse.jwt });
|
|
200
|
+
const whoIsResponse = await this.raceWithStop(whoIsRequest.fire({ jwt: refreshResponse.jwt }));
|
|
77
201
|
return {
|
|
78
202
|
user: whoIsResponse.user,
|
|
79
203
|
jwt: refreshResponse.jwt,
|
|
@@ -91,4 +215,47 @@ export class IdpGlobalServerClient {
|
|
|
91
215
|
}
|
|
92
216
|
return baseUrl;
|
|
93
217
|
}
|
|
218
|
+
|
|
219
|
+
private requireBackendToken(backendTokenArg?: string): string {
|
|
220
|
+
if (!backendTokenArg) {
|
|
221
|
+
throw new Error('backendToken is required for backend JWT validation APIs');
|
|
222
|
+
}
|
|
223
|
+
return backendTokenArg;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async applyBackendServiceTag(
|
|
227
|
+
socketArg: plugins.typedsocket.TypedSocket,
|
|
228
|
+
backendTokenArg: string
|
|
229
|
+
): Promise<void> {
|
|
230
|
+
await this.raceWithStop(socketArg.setTag<plugins.idpInterfaces.tags.ITag_BackendService>('backend-service', {
|
|
231
|
+
backendToken: backendTokenArg,
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private createConnectionDeferred() {
|
|
236
|
+
const deferred = plugins.smartpromise.defer<plugins.typedsocket.TypedSocket>();
|
|
237
|
+
deferred.promise.catch(() => undefined);
|
|
238
|
+
return deferred;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private raceWithStop<T>(promiseArg: Promise<T>): Promise<T> {
|
|
242
|
+
promiseArg.catch(() => undefined);
|
|
243
|
+
if (this.stopping) {
|
|
244
|
+
return Promise.reject(new Error('IdpGlobalServerClient stopped'));
|
|
245
|
+
}
|
|
246
|
+
return new Promise<T>((resolve, reject) => {
|
|
247
|
+
const stopReject = (errorArg: Error) => reject(errorArg);
|
|
248
|
+
this.stopWaiters.add(stopReject);
|
|
249
|
+
promiseArg.then(resolve, reject).finally(() => {
|
|
250
|
+
this.stopWaiters.delete(stopReject);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private rejectStopWaiters(errorArg: Error): void {
|
|
256
|
+
for (const stopWaiter of this.stopWaiters) {
|
|
257
|
+
stopWaiter(errorArg);
|
|
258
|
+
}
|
|
259
|
+
this.stopWaiters.clear();
|
|
260
|
+
}
|
|
94
261
|
}
|