docpouch-client 1.0.4 → 1.1.0
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/README.md +9 -17
- package/coverage/lcov-report/index.html +19 -19
- package/coverage/lcov.info +667 -534
- package/dist/index.d.ts +57 -1
- package/dist/index.js +157 -7
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Socket } from "socket.io-client";
|
|
2
2
|
/**
|
|
3
3
|
* Client for interacting with docPouch API.
|
|
4
|
+
* Supports JWT and OIDC authentication with comprehensive logout capabilities.
|
|
5
|
+
* Provides event emitter functionality for logout events.
|
|
4
6
|
*/
|
|
5
7
|
export default class docPouchClient {
|
|
6
8
|
/**
|
|
@@ -49,6 +51,10 @@ export default class docPouchClient {
|
|
|
49
51
|
* @type {boolean}
|
|
50
52
|
*/
|
|
51
53
|
private connectionInProgress;
|
|
54
|
+
/**
|
|
55
|
+
* Event emitter for logout events
|
|
56
|
+
*/
|
|
57
|
+
private events;
|
|
52
58
|
/**
|
|
53
59
|
* Creates an instance of docPouchClient.
|
|
54
60
|
*
|
|
@@ -227,9 +233,43 @@ export default class docPouchClient {
|
|
|
227
233
|
/**
|
|
228
234
|
* Logs out the client, clearing all tokens and disconnecting WebSocket.
|
|
229
235
|
*
|
|
236
|
+
* @param {LogoutOptions} [options] - Logout options for OIDC logout
|
|
230
237
|
* @returns {Promise<void>}
|
|
231
238
|
*/
|
|
232
|
-
logout(): Promise<void>;
|
|
239
|
+
logout(options?: LogoutOptions): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* Explicitly logout from OIDC provider
|
|
242
|
+
* Redirects to /end_session endpoint
|
|
243
|
+
*
|
|
244
|
+
* @param {LogoutOptions} [options] - Logout options
|
|
245
|
+
* @returns {Promise<void>}
|
|
246
|
+
*/
|
|
247
|
+
logoutOidc(options?: LogoutOptions): Promise<void>;
|
|
248
|
+
/**
|
|
249
|
+
* Logout from JWT (client-side only)
|
|
250
|
+
*
|
|
251
|
+
* @returns {Promise<void>}
|
|
252
|
+
*/
|
|
253
|
+
logoutJwt(): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Listen for logout events
|
|
256
|
+
*/
|
|
257
|
+
onLogout(callback: () => void): void;
|
|
258
|
+
/**
|
|
259
|
+
* Listen for OIDC logout specifically
|
|
260
|
+
*/
|
|
261
|
+
onOidcLogout(callback: () => void): void;
|
|
262
|
+
/**
|
|
263
|
+
* Listen for JWT logout specifically
|
|
264
|
+
*/
|
|
265
|
+
onJwtLogout(callback: () => void): void;
|
|
266
|
+
/**
|
|
267
|
+
* Check if user was just logged out (after redirect from /end_session)
|
|
268
|
+
* Checks URL for logout indicator or checks localStorage
|
|
269
|
+
*
|
|
270
|
+
* @returns {boolean} True if user was just logged out
|
|
271
|
+
*/
|
|
272
|
+
wasJustLoggedOut(): boolean;
|
|
233
273
|
/**
|
|
234
274
|
* Registers a new OIDC client with the server (dynamic client registration).
|
|
235
275
|
*
|
|
@@ -296,6 +336,16 @@ export default class docPouchClient {
|
|
|
296
336
|
private generateCodeVerifier;
|
|
297
337
|
private generateCodeChallenge;
|
|
298
338
|
private generateState;
|
|
339
|
+
/**
|
|
340
|
+
* Get post_logout_redirect_uri after OIDC logout
|
|
341
|
+
*
|
|
342
|
+
* @returns {string | null} The post logout redirect URI or null if not set
|
|
343
|
+
*/
|
|
344
|
+
getPostLogoutRedirectUri(): string | null;
|
|
345
|
+
/**
|
|
346
|
+
* Emit an event
|
|
347
|
+
*/
|
|
348
|
+
private emit;
|
|
299
349
|
}
|
|
300
350
|
export interface I_UserEntry extends I_UserCreation {
|
|
301
351
|
_id: string;
|
|
@@ -335,6 +385,10 @@ export interface I_LoginResponse {
|
|
|
335
385
|
userName: string;
|
|
336
386
|
expiresIn?: number;
|
|
337
387
|
}
|
|
388
|
+
export interface LogoutOptions {
|
|
389
|
+
redirectUri?: string;
|
|
390
|
+
idTokenHint?: string;
|
|
391
|
+
}
|
|
338
392
|
export interface I_OidcConfig {
|
|
339
393
|
issuer: string;
|
|
340
394
|
clientId: string;
|
|
@@ -342,6 +396,7 @@ export interface I_OidcConfig {
|
|
|
342
396
|
scope?: string;
|
|
343
397
|
scopes?: string[];
|
|
344
398
|
clientSecret?: string;
|
|
399
|
+
postLogoutRedirectUri?: string;
|
|
345
400
|
}
|
|
346
401
|
export interface I_OidcTokenResponse {
|
|
347
402
|
accessToken: string;
|
|
@@ -401,6 +456,7 @@ export interface I_DocumentCreation {
|
|
|
401
456
|
shareWithGroup: boolean;
|
|
402
457
|
shareWithDepartment: boolean;
|
|
403
458
|
public: boolean;
|
|
459
|
+
anonymous?: boolean;
|
|
404
460
|
}
|
|
405
461
|
export interface I_DocumentCreationOwned extends I_DocumentCreation {
|
|
406
462
|
owner: string;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { io } from "socket.io-client";
|
|
|
2
2
|
import packetJson from '../package.json';
|
|
3
3
|
/**
|
|
4
4
|
* Client for interacting with docPouch API.
|
|
5
|
+
* Supports JWT and OIDC authentication with comprehensive logout capabilities.
|
|
6
|
+
* Provides event emitter functionality for logout events.
|
|
5
7
|
*/
|
|
6
8
|
export default class docPouchClient {
|
|
7
9
|
/**
|
|
@@ -40,6 +42,10 @@ export default class docPouchClient {
|
|
|
40
42
|
* @type {boolean}
|
|
41
43
|
*/
|
|
42
44
|
this.connectionInProgress = false;
|
|
45
|
+
/**
|
|
46
|
+
* Event emitter for logout events
|
|
47
|
+
*/
|
|
48
|
+
this.events = {};
|
|
43
49
|
this.baseUrl = host;
|
|
44
50
|
const socketUrl = host.includes('://') ? host : `https://${host}`;
|
|
45
51
|
const socketUrlWithPort = socketUrl.includes(':') && !socketUrl.endsWith(':')
|
|
@@ -411,11 +417,15 @@ export default class docPouchClient {
|
|
|
411
417
|
return true;
|
|
412
418
|
}
|
|
413
419
|
restoreOidcSession() {
|
|
414
|
-
if (typeof window === 'undefined' || !window.localStorage)
|
|
415
|
-
return false;
|
|
416
420
|
const stored = localStorage.getItem('docpouch_oidc_session');
|
|
417
|
-
if (!stored)
|
|
421
|
+
if (!stored) {
|
|
422
|
+
// If no session, check if user was just logged out
|
|
423
|
+
if (this.wasJustLoggedOut()) {
|
|
424
|
+
// User was logged out, emit oidc-logout event
|
|
425
|
+
this.emit('oidc-logout');
|
|
426
|
+
}
|
|
418
427
|
return false;
|
|
428
|
+
}
|
|
419
429
|
try {
|
|
420
430
|
const session = JSON.parse(stored);
|
|
421
431
|
if (!session.accessToken || Date.now() >= session.expiry) {
|
|
@@ -474,10 +484,32 @@ export default class docPouchClient {
|
|
|
474
484
|
/**
|
|
475
485
|
* Logs out the client, clearing all tokens and disconnecting WebSocket.
|
|
476
486
|
*
|
|
487
|
+
* @param {LogoutOptions} [options] - Logout options for OIDC logout
|
|
477
488
|
* @returns {Promise<void>}
|
|
478
489
|
*/
|
|
479
|
-
async logout() {
|
|
490
|
+
async logout(options) {
|
|
480
491
|
const wasOidc = this.authMethod === 'oidc';
|
|
492
|
+
// For OIDC, redirect to /end_session endpoint
|
|
493
|
+
if (wasOidc && this.oidcConfig && typeof window !== 'undefined') {
|
|
494
|
+
const redirectUri = options?.redirectUri || this.oidcConfig.redirectUri || window.location.origin;
|
|
495
|
+
let url = `${this.oidcConfig.issuer}/end_session?post_logout_redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
496
|
+
// Add id_token_hint if available
|
|
497
|
+
const idToken = options?.idTokenHint || this.oidcIdToken;
|
|
498
|
+
if (idToken) {
|
|
499
|
+
url += `&id_token_hint=${idToken}`;
|
|
500
|
+
}
|
|
501
|
+
// Clear tokens before redirect
|
|
502
|
+
this.authToken = null;
|
|
503
|
+
this.clearOidcTokens();
|
|
504
|
+
this.authMethod = 'none';
|
|
505
|
+
if (this.socket.connected)
|
|
506
|
+
this.socket.disconnect();
|
|
507
|
+
// Redirect to OIDC logout endpoint
|
|
508
|
+
window.location.href = url;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// For JWT or no auth, just clear local state
|
|
512
|
+
const wasJwt = this.authMethod === 'jwt';
|
|
481
513
|
this.authToken = null;
|
|
482
514
|
this.clearOidcTokens();
|
|
483
515
|
this.authMethod = 'none';
|
|
@@ -487,11 +519,113 @@ export default class docPouchClient {
|
|
|
487
519
|
(window.location.search.includes('code=') || window.location.search.includes('state='))) {
|
|
488
520
|
window.history.replaceState({}, '', window.location.pathname);
|
|
489
521
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
522
|
+
// Emit appropriate logout events
|
|
523
|
+
if (wasJwt) {
|
|
524
|
+
this.emit('jwt-logout');
|
|
525
|
+
this.emit('logout');
|
|
526
|
+
}
|
|
527
|
+
else if (!wasOidc) {
|
|
528
|
+
// No auth case
|
|
529
|
+
this.emit('logout');
|
|
493
530
|
}
|
|
494
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Explicitly logout from OIDC provider
|
|
534
|
+
* Redirects to /end_session endpoint
|
|
535
|
+
*
|
|
536
|
+
* @param {LogoutOptions} [options] - Logout options
|
|
537
|
+
* @returns {Promise<void>}
|
|
538
|
+
*/
|
|
539
|
+
async logoutOidc(options) {
|
|
540
|
+
if (this.authMethod !== 'oidc') {
|
|
541
|
+
throw new Error('Not logged in with OIDC');
|
|
542
|
+
}
|
|
543
|
+
if (!this.oidcConfig?.issuer) {
|
|
544
|
+
throw new Error('OIDC issuer not configured');
|
|
545
|
+
}
|
|
546
|
+
const { redirectUri = this.oidcConfig.redirectUri || window.location.origin, idTokenHint } = options || {};
|
|
547
|
+
let url = `${this.oidcConfig.issuer}/end_session?post_logout_redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
548
|
+
if (idTokenHint) {
|
|
549
|
+
url += `&id_token_hint=${idTokenHint}`;
|
|
550
|
+
}
|
|
551
|
+
else if (this.oidcIdToken) {
|
|
552
|
+
url += `&id_token_hint=${this.oidcIdToken}`;
|
|
553
|
+
}
|
|
554
|
+
// Clear tokens before redirect
|
|
555
|
+
this.authToken = null;
|
|
556
|
+
this.clearOidcTokens();
|
|
557
|
+
this.authMethod = 'none';
|
|
558
|
+
if (this.socket.connected)
|
|
559
|
+
this.socket.disconnect();
|
|
560
|
+
// Emit OIDC logout event
|
|
561
|
+
this.emit('oidc-logout');
|
|
562
|
+
this.emit('logout');
|
|
563
|
+
window.location.href = url;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Logout from JWT (client-side only)
|
|
567
|
+
*
|
|
568
|
+
* @returns {Promise<void>}
|
|
569
|
+
*/
|
|
570
|
+
async logoutJwt() {
|
|
571
|
+
if (this.authMethod !== 'jwt') {
|
|
572
|
+
throw new Error('Not logged in with JWT');
|
|
573
|
+
}
|
|
574
|
+
this.authToken = null;
|
|
575
|
+
this.authMethod = 'none';
|
|
576
|
+
if (this.socket.connected)
|
|
577
|
+
this.socket.disconnect();
|
|
578
|
+
if (typeof window !== 'undefined' && window.location &&
|
|
579
|
+
(window.location.search.includes('code=') || window.location.search.includes('state='))) {
|
|
580
|
+
window.history.replaceState({}, '', window.location.pathname);
|
|
581
|
+
}
|
|
582
|
+
// Emit JWT logout event
|
|
583
|
+
if (this.events['jwt-logout']) {
|
|
584
|
+
this.events['jwt-logout']();
|
|
585
|
+
}
|
|
586
|
+
if (this.events['logout']) {
|
|
587
|
+
this.events['logout']();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Listen for logout events
|
|
592
|
+
*/
|
|
593
|
+
onLogout(callback) {
|
|
594
|
+
this.events['logout'] = callback;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Listen for OIDC logout specifically
|
|
598
|
+
*/
|
|
599
|
+
onOidcLogout(callback) {
|
|
600
|
+
this.events['oidc-logout'] = callback;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Listen for JWT logout specifically
|
|
604
|
+
*/
|
|
605
|
+
onJwtLogout(callback) {
|
|
606
|
+
this.events['jwt-logout'] = callback;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Check if user was just logged out (after redirect from /end_session)
|
|
610
|
+
* Checks URL for logout indicator or checks localStorage
|
|
611
|
+
*
|
|
612
|
+
* @returns {boolean} True if user was just logged out
|
|
613
|
+
*/
|
|
614
|
+
wasJustLoggedOut() {
|
|
615
|
+
// Check URL query parameter
|
|
616
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
617
|
+
if (urlParams.get('logout') === 'true') {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
// Check localStorage flag
|
|
621
|
+
const lastAuthMethod = localStorage.getItem('lastAuthMethod');
|
|
622
|
+
const currentAuthMethod = localStorage.getItem('authMethod');
|
|
623
|
+
// If last was OIDC/JWT but current is none, was logged out
|
|
624
|
+
if (lastAuthMethod && currentAuthMethod === null) {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
495
629
|
// OIDC Dynamic Client Registration Methods
|
|
496
630
|
/**
|
|
497
631
|
* Registers a new OIDC client with the server (dynamic client registration).
|
|
@@ -747,4 +881,20 @@ export default class docPouchClient {
|
|
|
747
881
|
crypto.getRandomValues(array);
|
|
748
882
|
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
749
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Get post_logout_redirect_uri after OIDC logout
|
|
886
|
+
*
|
|
887
|
+
* @returns {string | null} The post logout redirect URI or null if not set
|
|
888
|
+
*/
|
|
889
|
+
getPostLogoutRedirectUri() {
|
|
890
|
+
return localStorage.getItem('postLogoutRedirectUri');
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Emit an event
|
|
894
|
+
*/
|
|
895
|
+
emit(event) {
|
|
896
|
+
if (this.events[event]) {
|
|
897
|
+
this.events[event]();
|
|
898
|
+
}
|
|
899
|
+
}
|
|
750
900
|
}
|