@telicent-oss/fe-auth-lib 0.0.1 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telicent-oss/fe-auth-lib",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "description": "OAuth2 client library for Telicent Authentication Server",
@@ -50,5 +50,5 @@
50
50
  "engines": {
51
51
  "node": ">=20.19.0"
52
52
  },
53
- "gitHead": "c87206544b5b218fd0f964f9b544d44c78b929fd"
53
+ "gitHead": "8d5a492c674cc5d8af0be2f20341655c6231397f"
54
54
  }
@@ -37,14 +37,14 @@ export interface UserInfo {
37
37
  email: string;
38
38
  /** Preferred display name - NOT NULL in DB */
39
39
  preferred_name: string;
40
- /** Is Active - NOT NULL in DB */
41
- isActive: boolean;
42
- /** Groups - NOT NULL in DB */
43
- groups: string[];
44
- /** Roles - NOT NULL in DB */
45
- roles: string[];
46
- /** Permissions - NOT NULL in DB */
47
- permissions: string[];
40
+ /** Is Active - from ID token custom claim */
41
+ isActive?: boolean;
42
+ /** Groups - optional (not present in ID token) */
43
+ groups?: string[];
44
+ /** Roles - optional (not present in ID token) */
45
+ roles?: string[];
46
+ /** Permissions - optional (not present in ID token) */
47
+ permissions?: string[];
48
48
  // Standard OIDC claims (always present)
49
49
  /** Token issuer URL */
50
50
  iss: string;
@@ -74,7 +74,7 @@ export interface UserInfo {
74
74
  token_expired?: boolean;
75
75
  /** Token expiration timestamp (ISO string) */
76
76
  token_expires_at?: string;
77
- /** Source of user info ('id_token' or 'oauth2_userinfo_api') */
77
+ /** Source of user info (id_token; /userinfo removed) */
78
78
  source?: string;
79
79
  /** External identity provider details */
80
80
  externalProvider?: Record<string, unknown>;
@@ -343,22 +343,11 @@ declare class AuthServerOAuth2Client {
343
343
  getUserInfo(): UserInfo | null;
344
344
 
345
345
  /**
346
- * Get fresh user info from OAuth2 userinfo endpoint
346
+ * Returns ID token claims; /userinfo is no longer available.
347
347
  *
348
- * Fetches current user data from auth server. Slower than getUserInfo() but
349
- * guarantees fresh data. Use when you need up-to-date user information.
348
+ * Use getUserInfo() instead. This method remains for API compatibility.
350
349
  *
351
- * @returns Promise resolving to fresh user information
352
- * @throws {Error} If request fails or session invalid
353
- * @example
354
- * ```javascript
355
- * try {
356
- * const freshUserInfo = await authClient.getUserInfoFromAPI();
357
- * console.log("Fresh user data:", freshUserInfo);
358
- * } catch (error) {
359
- * console.error("Failed to get fresh user info:", error);
360
- * }
361
- * ```
350
+ * @returns Promise resolving to user information or null
362
351
  */
363
352
  getUserInfoFromAPI(): Promise<UserInfo | null>;
364
353
 
@@ -34,19 +34,14 @@ class AuthServerOAuth2Client {
34
34
  `Invalid AuthServerOAuth2Client configuration: ${error.message}`
35
35
  );
36
36
  }
37
+ } else {
38
+ console.warn(
39
+ "⚠️ AuthServerOAuth2Client instantiated with undefined config"
40
+ );
41
+ return;
37
42
  }
38
43
 
39
44
  this.config = {
40
- clientId: "spa-client", // Default - should be overridden
41
- authServerUrl: "http://auth.telicent.localhost",
42
- redirectUri: "http://demo.telicent.localhost/callback.html", // Default - should be overridden
43
- popupRedirectUri: null, // Must be provided for popup flows
44
- scope: "openid email profile offline_access",
45
- apiUrl: "http://api.telicent.localhost",
46
- onLogout: () => {
47
- window.alert("You are now logged out. Redirecting to /");
48
- window.location.href = "/";
49
- },
50
45
  ...config,
51
46
  };
52
47
 
@@ -424,7 +419,6 @@ class AuthServerOAuth2Client {
424
419
  const result = await response.json();
425
420
  // set auth_session_id if user is already signed in
426
421
  sessionStorage.setItem("auth_id_token", result.id_token);
427
- console.log(result);
428
422
  return true;
429
423
  }
430
424
 
@@ -607,6 +601,7 @@ class AuthServerOAuth2Client {
607
601
  sub: payload.sub,
608
602
  email: payload.email,
609
603
  preferred_name: payload.preferred_name,
604
+ isActive: payload.isActive,
610
605
  iss: payload.iss,
611
606
  aud: payload.aud,
612
607
  exp: payload.exp,
@@ -652,25 +647,10 @@ class AuthServerOAuth2Client {
652
647
 
653
648
  // Get fresh user info from OAuth2 userinfo endpoint (UNIFIED ENDPOINT)
654
649
  async getUserInfoFromAPI() {
655
- try {
656
- const response = await this.makeAuthenticatedRequest(
657
- `${this.config.authServerUrl}/userinfo`
658
- );
659
-
660
- if (response.ok) {
661
- const data = await response.json();
662
- return {
663
- ...data,
664
- source: this.isCrossDomain
665
- ? "oauth2_userinfo_api_cross_domain"
666
- : "oauth2_userinfo_api_same_domain",
667
- };
668
- }
669
- return null;
670
- } catch (error) {
671
- console.error("Error getting user info from OAuth2 API:", error);
672
- return null;
673
- }
650
+ console.warn(
651
+ "getUserInfoFromAPI: /userinfo has been removed; returning ID token claims instead."
652
+ );
653
+ return this.getUserInfo();
674
654
  }
675
655
 
676
656
  // Get raw ID token from storage
@@ -734,9 +714,6 @@ class AuthServerOAuth2Client {
734
714
  const response = await fetch(url, requestOptions);
735
715
 
736
716
  if (response.status === 401) {
737
- // QUESTION: would I ever use this method to call session check?
738
- // I can only envisage scenarios where app endpoints get hit using this?
739
- //
740
717
  // Don't auto-logout during callback flow or logout operations to prevent infinite loops
741
718
  const isCallbackFlow =
742
719
  options.skipAutoLogout || url.includes("/session/idtoken");
package/src/schemas.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare const GetUserInfoSchema: z.ZodObject<{
9
9
  sub: z.ZodString;
10
10
  email: z.ZodString;
11
11
  preferred_name: z.ZodString;
12
+ isActive: z.ZodOptional<z.ZodBoolean>;
12
13
 
13
14
  // Standard OIDC claims (always present)
14
15
  iss: z.ZodString;
package/src/schemas.js CHANGED
@@ -4,11 +4,13 @@ let GetUserInfoSchema;
4
4
  let AuthServerOAuth2ClientConfigSchema;
5
5
 
6
6
  try {
7
- if (typeof require !== 'undefined') {
8
- z = require('zod').z;
7
+ if (typeof require !== "undefined") {
8
+ z = require("zod").z;
9
9
  }
10
10
  } catch (e) {
11
- console.warn('Zod not available in this environment, schema validation will be disabled');
11
+ console.warn(
12
+ "Zod not available in this environment, schema validation will be disabled"
13
+ );
12
14
  }
13
15
 
14
16
  /**
@@ -17,64 +19,67 @@ try {
17
19
  */
18
20
  if (z) {
19
21
  GetUserInfoSchema = z.object({
20
- // Core user identity (from JWTConfig.java:169-171)
21
- sub: z.string(), // Always present
22
- email: z.string().email(), // NOT NULL in DB
23
- preferred_name: z.string(), // NOT NULL in DB
22
+ // Core user identity (from JWTConfig.java:169-171)
23
+ sub: z.string(), // Always present
24
+ email: z.string().email(), // NOT NULL in DB
25
+ preferred_name: z.string(), // NOT NULL in DB
26
+ isActive: z.boolean().optional(), // Custom claim from ID token
24
27
 
25
- // Standard OIDC claims (always present)
26
- iss: z.string(), // Issuer URL
27
- aud: z.string(), // Audience (client ID)
28
- exp: z.number(), // Expiration timestamp
29
- iat: z.number(), // Issued at timestamp
30
- jti: z.string(), // JWT ID
28
+ // Standard OIDC claims (always present)
29
+ iss: z.string(), // Issuer URL
30
+ aud: z.string(), // Audience (client ID)
31
+ exp: z.number(), // Expiration timestamp
32
+ iat: z.number(), // Issued at timestamp
33
+ jti: z.string(), // JWT ID
31
34
 
32
- // Optional OIDC claims (conditional)
33
- nonce: z.string().optional(), // Only if sent in auth request
34
- auth_time: z.number().optional(), // Authentication timestamp
35
- sid: z.string().optional(), // Session ID
36
- azp: z.string().optional(), // Authorized party
35
+ // Optional OIDC claims (conditional)
36
+ nonce: z.string().optional(), // Only if sent in auth request
37
+ auth_time: z.number().optional(), // Authentication timestamp
38
+ sid: z.string().optional(), // Session ID
39
+ azp: z.string().optional(), // Authorized party
37
40
 
38
- // FE client additions (check your oauth2Client implementation)
39
- name: z.string().optional(),
40
- token_expired: z.boolean().optional(),
41
- token_expires_at: z.string().optional(),
42
- source: z.string().optional(),
43
- externalProvider: z.record(z.unknown()).optional(),
41
+ // FE client additions (check your oauth2Client implementation)
42
+ name: z.string().optional(),
43
+ token_expired: z.boolean().optional(),
44
+ token_expires_at: z.string().optional(),
45
+ source: z.string().optional(),
46
+ externalProvider: z.record(z.unknown()).optional(),
44
47
  });
45
48
 
46
49
  /**
47
50
  * Zod schema for AuthServerOAuth2Client constructor config
48
51
  */
49
- AuthServerOAuth2ClientConfigSchema = z.object({
50
- // OAuth2 client identifier
51
- clientId: z.string().optional(),
52
- authServerUrl: z.string().url().optional(),
53
- redirectUri: z.string().url().optional(),
54
- popupRedirectUri: z.string().url().optional().nullable(),
55
- scope: z.string().optional(),
56
- apiUrl: z.string().url().optional(),
57
- onLogout: z.function().optional(),
58
- }).strict();
52
+ AuthServerOAuth2ClientConfigSchema = z
53
+ .object({
54
+ // OAuth2 client identifier
55
+ clientId: z.string(),
56
+ authServerUrl: z.string().url(),
57
+ redirectUri: z.string().url(),
58
+ popupRedirectUri: z.string().url(),
59
+ scope: z.string(),
60
+ onLogout: z.function(),
61
+ })
62
+ .strict();
59
63
  }
60
64
 
61
65
  // ES module and CommonJS exports
62
- if (typeof module !== 'undefined' && module.exports) {
66
+ if (typeof module !== "undefined" && module.exports) {
63
67
  module.exports = {
64
68
  GetUserInfoSchema,
65
- AuthServerOAuth2ClientConfigSchema
69
+ AuthServerOAuth2ClientConfigSchema,
66
70
  };
67
71
  module.exports.default = {
68
72
  GetUserInfoSchema,
69
- AuthServerOAuth2ClientConfigSchema
73
+ AuthServerOAuth2ClientConfigSchema,
70
74
  };
71
75
  }
72
76
 
73
- if (typeof exports !== 'undefined' && typeof module === 'undefined') {
77
+ if (typeof exports !== "undefined" && typeof module === "undefined") {
74
78
  exports.GetUserInfoSchema = GetUserInfoSchema;
75
- exports.AuthServerOAuth2ClientConfigSchema = AuthServerOAuth2ClientConfigSchema;
79
+ exports.AuthServerOAuth2ClientConfigSchema =
80
+ AuthServerOAuth2ClientConfigSchema;
76
81
  exports.default = {
77
82
  GetUserInfoSchema,
78
- AuthServerOAuth2ClientConfigSchema
83
+ AuthServerOAuth2ClientConfigSchema,
79
84
  };
80
85
  }
@@ -65,19 +65,19 @@ describe("AuthServerOAuth2ClientConfigSchema", () => {
65
65
 
66
66
  expect(results).toMatchInlineSnapshot(`
67
67
  [
68
- " absolute https URL",
69
- " absolute http URL",
70
- " null value",
71
- " omitted field",
72
- " all fields with absolute URLs",
73
- "✗ relative slash URL in popupRedirectUri → popupRedirectUri: Invalid url",
74
- "✗ relative dot-slash URL in popupRedirectUri → popupRedirectUri: Invalid url",
75
- "✗ URL without protocol in popupRedirectUri → popupRedirectUri: Invalid url",
76
- "✗ protocol-relative URL in popupRedirectUri → popupRedirectUri: Invalid url",
77
- "✗ relative URL in authServerUrl → authServerUrl: Invalid url",
78
- "✗ relative URL in redirectUri → redirectUri: Invalid url",
79
- "✗ relative URL in apiUrl → apiUrl: Invalid url",
80
- "✗ unknown property → root: Unrecognized key(s) in object: 'unknownProperty'",
68
+ " absolute https URL → clientId: Required, authServerUrl: Required, redirectUri: Required, scope: Required, onLogout: Required",
69
+ " absolute http URL → clientId: Required, authServerUrl: Required, redirectUri: Required, scope: Required, onLogout: Required",
70
+ " null value → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Expected string, received null, scope: Required, onLogout: Required",
71
+ " omitted field → authServerUrl: Required, redirectUri: Required, popupRedirectUri: Required, scope: Required, onLogout: Required",
72
+ " all fields with absolute URLs → onLogout: Required, root: Unrecognized key(s) in object: 'apiUrl'",
73
+ "✗ relative slash URL in popupRedirectUri → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Invalid url, scope: Required, onLogout: Required",
74
+ "✗ relative dot-slash URL in popupRedirectUri → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Invalid url, scope: Required, onLogout: Required",
75
+ "✗ URL without protocol in popupRedirectUri → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Invalid url, scope: Required, onLogout: Required",
76
+ "✗ protocol-relative URL in popupRedirectUri → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Invalid url, scope: Required, onLogout: Required",
77
+ "✗ relative URL in authServerUrl → clientId: Required, authServerUrl: Invalid url, redirectUri: Required, popupRedirectUri: Required, scope: Required, onLogout: Required",
78
+ "✗ relative URL in redirectUri → clientId: Required, authServerUrl: Required, redirectUri: Invalid url, popupRedirectUri: Required, scope: Required, onLogout: Required",
79
+ "✗ relative URL in apiUrl → clientId: Required, authServerUrl: Required, redirectUri: Required, popupRedirectUri: Required, scope: Required, onLogout: Required, root: Unrecognized key(s) in object: 'apiUrl'",
80
+ "✗ unknown property → authServerUrl: Required, redirectUri: Required, popupRedirectUri: Required, scope: Required, onLogout: Required, root: Unrecognized key(s) in object: 'unknownProperty'",
81
81
  ]
82
82
  `);
83
83
  });