@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 +2 -2
- package/src/AuthServerOAuth2Client.d.ts +12 -23
- package/src/AuthServerOAuth2Client.js +10 -33
- package/src/schemas.d.ts +1 -0
- package/src/schemas.js +45 -40
- package/src/schemas.test.js +13 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telicent-oss/fe-auth-lib",
|
|
3
|
-
"version": "0.0
|
|
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": "
|
|
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 -
|
|
41
|
-
isActive
|
|
42
|
-
/** Groups -
|
|
43
|
-
groups
|
|
44
|
-
/** Roles -
|
|
45
|
-
roles
|
|
46
|
-
/** Permissions -
|
|
47
|
-
permissions
|
|
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 (
|
|
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
|
-
*
|
|
346
|
+
* Returns ID token claims; /userinfo is no longer available.
|
|
347
347
|
*
|
|
348
|
-
*
|
|
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
|
|
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
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
package/src/schemas.js
CHANGED
|
@@ -4,11 +4,13 @@ let GetUserInfoSchema;
|
|
|
4
4
|
let AuthServerOAuth2ClientConfigSchema;
|
|
5
5
|
|
|
6
6
|
try {
|
|
7
|
-
if (typeof require !==
|
|
8
|
-
z = require(
|
|
7
|
+
if (typeof require !== "undefined") {
|
|
8
|
+
z = require("zod").z;
|
|
9
9
|
}
|
|
10
10
|
} catch (e) {
|
|
11
|
-
console.warn(
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 !==
|
|
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 !==
|
|
77
|
+
if (typeof exports !== "undefined" && typeof module === "undefined") {
|
|
74
78
|
exports.GetUserInfoSchema = GetUserInfoSchema;
|
|
75
|
-
exports.AuthServerOAuth2ClientConfigSchema =
|
|
79
|
+
exports.AuthServerOAuth2ClientConfigSchema =
|
|
80
|
+
AuthServerOAuth2ClientConfigSchema;
|
|
76
81
|
exports.default = {
|
|
77
82
|
GetUserInfoSchema,
|
|
78
|
-
AuthServerOAuth2ClientConfigSchema
|
|
83
|
+
AuthServerOAuth2ClientConfigSchema,
|
|
79
84
|
};
|
|
80
85
|
}
|
package/src/schemas.test.js
CHANGED
|
@@ -65,19 +65,19 @@ describe("AuthServerOAuth2ClientConfigSchema", () => {
|
|
|
65
65
|
|
|
66
66
|
expect(results).toMatchInlineSnapshot(`
|
|
67
67
|
[
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
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 →
|
|
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
|
});
|