docpouch-client 1.1.4 → 1.2.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/dist/index.d.ts CHANGED
@@ -364,6 +364,120 @@ export default class docPouchClient {
364
364
  * Emit an event
365
365
  */
366
366
  private emit;
367
+ /**
368
+ * Fetches the OIDC client configuration from the server's
369
+ * `/api/oidc-client-config` endpoint. This is the first step in any
370
+ * OIDC flow: call this method to discover whether OIDC is configured
371
+ * and, if so, obtain the issuer, clientId, redirectUri, etc.
372
+ *
373
+ * The returned object matches the {@link I_OidcClientConfig} shape which
374
+ * can be passed directly to {@link setOidcConfig} or
375
+ * {@link loginWithOidc}.
376
+ *
377
+ * @returns {Promise<I_OidcClientConfig | null>} The client config if
378
+ * OIDC is configured, or null if the server reports OIDC is not
379
+ * available.
380
+ */
381
+ fetchOidcClientConfig(): Promise<I_OidcClientConfig | null>;
382
+ /**
383
+ * Fetches the currently authenticated user's profile from the
384
+ * `/users/whoami` endpoint. Works with both JWT and OIDC
385
+ * authentication — the correct token is sent automatically.
386
+ *
387
+ * @returns {Promise<I_UserEntry | null>} The user profile, or null
388
+ * if the request fails or the user is not authenticated.
389
+ */
390
+ getCurrentUser(): Promise<I_UserEntry | null>;
391
+ /**
392
+ * Ensures an OIDC client is registered with the DocPouch server
393
+ * (dynamic client registration). If a `clientId` is found in
394
+ * localStorage under `docpouch_oidc_client_id`, it attempts to
395
+ * update the existing registration. If that fails (or no stored ID
396
+ * exists), a new client is registered via
397
+ * {@link registerOidcClient}.
398
+ *
399
+ * This method manages the `docpouch_oidc_client_id` localStorage
400
+ * key automatically. Call it before {@link loginWithOidc} when
401
+ * acting as a third-party relying party (i.e. not the built-in
402
+ * admin UI).
403
+ *
404
+ * @param {string} redirectUri - The URI the IdP should redirect to
405
+ * after login.
406
+ * @param {string} [registrationToken] - Initial access token for
407
+ * the `/oidc/reg` endpoint. If omitted the current auth token is
408
+ * used.
409
+ * @param {object} [options] - Additional options.
410
+ * @param {string} [options.clientName] - Display name for the
411
+ * client registration (default `'DocPouch Client'`).
412
+ * @param {string} [options.postLogoutRedirectUri] - If provided,
413
+ * also registers this URI as a post-logout redirect.
414
+ * @returns {Promise<string>} The resolved clientId.
415
+ */
416
+ ensureOidcClient(redirectUri: string, registrationToken?: string, options?: {
417
+ clientName?: string;
418
+ postLogoutRedirectUri?: string;
419
+ }): Promise<string>;
420
+ /**
421
+ * Attempts to restore an existing authentication session (JWT or
422
+ * OIDC) from browser storage. This is the recommended entry point
423
+ * on page load:
424
+ *
425
+ * 1. If an OIDC logout redirect was just completed, clears local
426
+ * state and returns `{method: 'none'}`.
427
+ * 2. If the URL contains `code` and `state` params, exchanges them
428
+ * for OIDC tokens via {@link handleOidcCallback}.
429
+ * 3. If a persisted OIDC session exists, restores it.
430
+ * 4. Falls back to a JWT token in localStorage.
431
+ *
432
+ * @returns {Promise<I_AuthState>} The authentication state after
433
+ * attempting restoration.
434
+ */
435
+ initAuth(): Promise<I_AuthState>;
436
+ /**
437
+ * Clears all local authentication state — JWT token, OIDC tokens,
438
+ * persisted sessions, and localStorage keys managed by this library.
439
+ * Disconnects the WebSocket if connected.
440
+ *
441
+ * This does NOT redirect to an OIDC end-session endpoint. For a
442
+ * full RP-initiated logout use {@link logout} or
443
+ * {@link logoutOidc}.
444
+ */
445
+ clearAuth(): void;
446
+ /**
447
+ * Persists the current authentication method to localStorage so it
448
+ * can be restored after a page reload.
449
+ *
450
+ * @param {'jwt' | 'oidc'} method - The auth method to persist.
451
+ */
452
+ persistAuthMethod(method: 'jwt' | 'oidc'): void;
453
+ /**
454
+ * Removes all localStorage keys managed by docpouch-client:
455
+ * `authToken`, `authMethod`, `docpouch_oidc_session`,
456
+ * `docpouch_oidc_client_id`, `docpouch_registration_token`,
457
+ * `isAdmin`, and `postLogoutRedirectUri`.
458
+ */
459
+ clearPersistedAuthState(): void;
460
+ /**
461
+ * Convenience method to fetch the OIDC client config from the server
462
+ * and immediately initiate the OIDC login redirect. This combines
463
+ * {@link fetchOidcClientConfig} and {@link loginWithOidc} into a
464
+ * single call.
465
+ *
466
+ * @param {string} [registrationToken] - Optional initial access
467
+ * token for dynamic client registration. If provided and no
468
+ * client config is available from the server, the method attempts
469
+ * dynamic registration via {@link ensureOidcClient}.
470
+ * @returns {Promise<void>}
471
+ */
472
+ startOidcLogin(registrationToken?: string): Promise<void>;
473
+ /**
474
+ * Derives the OIDC issuer URL from the client's baseUrl. By
475
+ * convention the DocPouch OIDC provider is mounted at
476
+ * `${baseUrl}/oidc`.
477
+ *
478
+ * @returns {string} The issuer URL.
479
+ */
480
+ getOidcIssuer(): string;
367
481
  }
368
482
  export interface I_UserEntry extends I_UserCreation {
369
483
  _id: string;
@@ -416,6 +530,10 @@ export interface I_OidcConfig {
416
530
  clientSecret?: string;
417
531
  postLogoutRedirectUri?: string;
418
532
  }
533
+ export interface I_OidcClientConfig extends I_OidcConfig {
534
+ configured?: boolean;
535
+ apiBaseUrl?: string;
536
+ }
419
537
  export interface I_OidcTokenResponse {
420
538
  accessToken: string;
421
539
  refreshToken?: string;
package/dist/index.js CHANGED
@@ -970,4 +970,279 @@ export default class docPouchClient {
970
970
  this.events[event]();
971
971
  }
972
972
  }
973
+ // Convenience OIDC methods
974
+ /**
975
+ * Fetches the OIDC client configuration from the server's
976
+ * `/api/oidc-client-config` endpoint. This is the first step in any
977
+ * OIDC flow: call this method to discover whether OIDC is configured
978
+ * and, if so, obtain the issuer, clientId, redirectUri, etc.
979
+ *
980
+ * The returned object matches the {@link I_OidcClientConfig} shape which
981
+ * can be passed directly to {@link setOidcConfig} or
982
+ * {@link loginWithOidc}.
983
+ *
984
+ * @returns {Promise<I_OidcClientConfig | null>} The client config if
985
+ * OIDC is configured, or null if the server reports OIDC is not
986
+ * available.
987
+ */
988
+ async fetchOidcClientConfig() {
989
+ try {
990
+ const data = await this.request('/api/oidc-client-config', 'GET', undefined, false);
991
+ if (!data)
992
+ return null;
993
+ // The server may or may not include `configured`. If it has an
994
+ // `issuer` field we consider it valid regardless.
995
+ if (data.configured === false)
996
+ return null;
997
+ if (!data.issuer)
998
+ return null;
999
+ return data;
1000
+ }
1001
+ catch {
1002
+ return null;
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Fetches the currently authenticated user's profile from the
1007
+ * `/users/whoami` endpoint. Works with both JWT and OIDC
1008
+ * authentication — the correct token is sent automatically.
1009
+ *
1010
+ * @returns {Promise<I_UserEntry | null>} The user profile, or null
1011
+ * if the request fails or the user is not authenticated.
1012
+ */
1013
+ async getCurrentUser() {
1014
+ try {
1015
+ return await this.request('/users/whoami', 'GET');
1016
+ }
1017
+ catch {
1018
+ return null;
1019
+ }
1020
+ }
1021
+ /**
1022
+ * Ensures an OIDC client is registered with the DocPouch server
1023
+ * (dynamic client registration). If a `clientId` is found in
1024
+ * localStorage under `docpouch_oidc_client_id`, it attempts to
1025
+ * update the existing registration. If that fails (or no stored ID
1026
+ * exists), a new client is registered via
1027
+ * {@link registerOidcClient}.
1028
+ *
1029
+ * This method manages the `docpouch_oidc_client_id` localStorage
1030
+ * key automatically. Call it before {@link loginWithOidc} when
1031
+ * acting as a third-party relying party (i.e. not the built-in
1032
+ * admin UI).
1033
+ *
1034
+ * @param {string} redirectUri - The URI the IdP should redirect to
1035
+ * after login.
1036
+ * @param {string} [registrationToken] - Initial access token for
1037
+ * the `/oidc/reg` endpoint. If omitted the current auth token is
1038
+ * used.
1039
+ * @param {object} [options] - Additional options.
1040
+ * @param {string} [options.clientName] - Display name for the
1041
+ * client registration (default `'DocPouch Client'`).
1042
+ * @param {string} [options.postLogoutRedirectUri] - If provided,
1043
+ * also registers this URI as a post-logout redirect.
1044
+ * @returns {Promise<string>} The resolved clientId.
1045
+ */
1046
+ async ensureOidcClient(redirectUri, registrationToken, options) {
1047
+ const clientName = options?.clientName || 'DocPouch Client';
1048
+ const postLogoutRedirectUri = options?.postLogoutRedirectUri;
1049
+ const storedClientId = typeof window !== 'undefined' && window.localStorage
1050
+ ? localStorage.getItem('docpouch_oidc_client_id')
1051
+ : null;
1052
+ const postLogoutUris = postLogoutRedirectUri
1053
+ ? [redirectUri, postLogoutRedirectUri]
1054
+ : [redirectUri];
1055
+ const payload = {
1056
+ client_name: clientName,
1057
+ redirect_uris: [redirectUri],
1058
+ post_logout_redirect_uris: postLogoutUris,
1059
+ grant_types: ['authorization_code', 'refresh_token'],
1060
+ response_types: ['code'],
1061
+ token_endpoint_auth_method: 'none',
1062
+ application_type: 'web',
1063
+ };
1064
+ if (storedClientId) {
1065
+ try {
1066
+ await this.updateOidcClient(storedClientId, payload, registrationToken);
1067
+ return storedClientId;
1068
+ }
1069
+ catch {
1070
+ // Update failed, will re-register below
1071
+ }
1072
+ }
1073
+ const response = await this.registerOidcClient(payload, registrationToken);
1074
+ if (typeof window !== 'undefined' && window.localStorage) {
1075
+ localStorage.setItem('docpouch_oidc_client_id', response.client_id);
1076
+ }
1077
+ if (response.registration_access_token && typeof window !== 'undefined' && window.localStorage) {
1078
+ localStorage.setItem('docpouch_registration_token', response.registration_access_token);
1079
+ }
1080
+ return response.client_id;
1081
+ }
1082
+ /**
1083
+ * Attempts to restore an existing authentication session (JWT or
1084
+ * OIDC) from browser storage. This is the recommended entry point
1085
+ * on page load:
1086
+ *
1087
+ * 1. If an OIDC logout redirect was just completed, clears local
1088
+ * state and returns `{method: 'none'}`.
1089
+ * 2. If the URL contains `code` and `state` params, exchanges them
1090
+ * for OIDC tokens via {@link handleOidcCallback}.
1091
+ * 3. If a persisted OIDC session exists, restores it.
1092
+ * 4. Falls back to a JWT token in localStorage.
1093
+ *
1094
+ * @returns {Promise<I_AuthState>} The authentication state after
1095
+ * attempting restoration.
1096
+ */
1097
+ async initAuth() {
1098
+ // 1. Check for logout redirect
1099
+ if (this.wasJustLoggedOut()) {
1100
+ this.authToken = null;
1101
+ this.clearOidcTokens();
1102
+ this.authMethod = 'none';
1103
+ this.clearPersistedAuthState();
1104
+ return { method: 'none', token: null, isAdmin: false, userName: '' };
1105
+ }
1106
+ // 2. Try OIDC callback (code/state in URL)
1107
+ try {
1108
+ const handled = await this.handleOidcCallback();
1109
+ if (handled) {
1110
+ const token = this.getToken();
1111
+ if (token) {
1112
+ this.persistAuthMethod('oidc');
1113
+ return { method: 'oidc', token, isAdmin: false, userName: '' };
1114
+ }
1115
+ }
1116
+ }
1117
+ catch (e) {
1118
+ console.error('OIDC callback error:', e);
1119
+ // Clean up URL: remove leftover code/state params so they
1120
+ // don't trigger another failed attempt on reload.
1121
+ if (typeof window !== 'undefined' && window.location &&
1122
+ (window.location.search.includes('code=') || window.location.search.includes('state='))) {
1123
+ window.history.replaceState({}, '', window.location.pathname);
1124
+ }
1125
+ // A failed OIDC callback means the flow is invalid; clear any
1126
+ // stale OIDC session so restoreOidcSession() won't restore it.
1127
+ this.clearOidcTokens();
1128
+ }
1129
+ // 3. Try OIDC session restore
1130
+ if (this.restoreOidcSession()) {
1131
+ const token = this.getToken();
1132
+ if (token) {
1133
+ this.persistAuthMethod('oidc');
1134
+ return { method: 'oidc', token, isAdmin: false, userName: '' };
1135
+ }
1136
+ }
1137
+ // 4. Fallback to JWT token
1138
+ if (typeof window !== 'undefined' && window.localStorage) {
1139
+ const storedToken = localStorage.getItem('authToken');
1140
+ if (storedToken) {
1141
+ this.authToken = storedToken;
1142
+ this.authMethod = 'jwt';
1143
+ return { method: 'jwt', token: storedToken, isAdmin: false, userName: '' };
1144
+ }
1145
+ }
1146
+ return { method: 'none', token: null, isAdmin: false, userName: '' };
1147
+ }
1148
+ /**
1149
+ * Clears all local authentication state — JWT token, OIDC tokens,
1150
+ * persisted sessions, and localStorage keys managed by this library.
1151
+ * Disconnects the WebSocket if connected.
1152
+ *
1153
+ * This does NOT redirect to an OIDC end-session endpoint. For a
1154
+ * full RP-initiated logout use {@link logout} or
1155
+ * {@link logoutOidc}.
1156
+ */
1157
+ clearAuth() {
1158
+ this.authToken = null;
1159
+ this.clearOidcTokens();
1160
+ this.authMethod = 'none';
1161
+ if (this.socket.connected) {
1162
+ this.socket.disconnect();
1163
+ }
1164
+ this.clearPersistedAuthState();
1165
+ }
1166
+ /**
1167
+ * Persists the current authentication method to localStorage so it
1168
+ * can be restored after a page reload.
1169
+ *
1170
+ * @param {'jwt' | 'oidc'} method - The auth method to persist.
1171
+ */
1172
+ persistAuthMethod(method) {
1173
+ if (typeof window !== 'undefined' && window.localStorage) {
1174
+ localStorage.setItem('authMethod', method);
1175
+ }
1176
+ }
1177
+ /**
1178
+ * Removes all localStorage keys managed by docpouch-client:
1179
+ * `authToken`, `authMethod`, `docpouch_oidc_session`,
1180
+ * `docpouch_oidc_client_id`, `docpouch_registration_token`,
1181
+ * `isAdmin`, and `postLogoutRedirectUri`.
1182
+ */
1183
+ clearPersistedAuthState() {
1184
+ if (typeof window !== 'undefined' && window.localStorage) {
1185
+ localStorage.removeItem('authToken');
1186
+ localStorage.removeItem('authMethod');
1187
+ localStorage.removeItem('docpouch_oidc_session');
1188
+ localStorage.removeItem('docpouch_oidc_client_id');
1189
+ localStorage.removeItem('docpouch_registration_token');
1190
+ localStorage.removeItem('isAdmin');
1191
+ localStorage.removeItem('postLogoutRedirectUri');
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Convenience method to fetch the OIDC client config from the server
1196
+ * and immediately initiate the OIDC login redirect. This combines
1197
+ * {@link fetchOidcClientConfig} and {@link loginWithOidc} into a
1198
+ * single call.
1199
+ *
1200
+ * @param {string} [registrationToken] - Optional initial access
1201
+ * token for dynamic client registration. If provided and no
1202
+ * client config is available from the server, the method attempts
1203
+ * dynamic registration via {@link ensureOidcClient}.
1204
+ * @returns {Promise<void>}
1205
+ */
1206
+ async startOidcLogin(registrationToken) {
1207
+ let config = this.oidcConfig;
1208
+ if (!config) {
1209
+ config = await this.fetchOidcClientConfig();
1210
+ }
1211
+ if (!config && registrationToken) {
1212
+ const redirectUri = typeof window !== 'undefined'
1213
+ ? window.location.origin + window.location.pathname
1214
+ : '';
1215
+ const clientId = await this.ensureOidcClient(redirectUri, registrationToken, {
1216
+ postLogoutRedirectUri: redirectUri,
1217
+ });
1218
+ const issuer = this.getOidcIssuer();
1219
+ config = {
1220
+ issuer,
1221
+ clientId,
1222
+ redirectUri,
1223
+ postLogoutRedirectUri: redirectUri,
1224
+ scope: 'openid profile email offline_access',
1225
+ };
1226
+ }
1227
+ if (!config) {
1228
+ throw new Error('OIDC is not configured on this server');
1229
+ }
1230
+ this.setOidcConfig(config);
1231
+ await this.loginWithOidc(config);
1232
+ }
1233
+ /**
1234
+ * Derives the OIDC issuer URL from the client's baseUrl. By
1235
+ * convention the DocPouch OIDC provider is mounted at
1236
+ * `${baseUrl}/oidc`.
1237
+ *
1238
+ * @returns {string} The issuer URL.
1239
+ */
1240
+ getOidcIssuer() {
1241
+ const trimmedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
1242
+ const hasExplicitPort = /:\d+(?=\/|$)/.test(trimmedBaseUrl);
1243
+ const normalizedBaseUrl = !hasExplicitPort && this.port
1244
+ ? `${trimmedBaseUrl}:${this.port}`
1245
+ : trimmedBaseUrl;
1246
+ return `${normalizedBaseUrl}/oidc`;
1247
+ }
973
1248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpouch-client",
3
- "version": "1.1.4",
3
+ "version": "1.2.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {