@clocklobster/cognito-client 1.0.0 → 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 +692 -667
- package/dist/index.d.ts +22 -0
- package/dist/index.js +52 -0
- package/package.json +8 -2
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,15 @@ export interface CognitoClientOptions {
|
|
|
112
112
|
/** Current page path + query, used to build the `?returnTo=` destination. */
|
|
113
113
|
getCurrentPath?: () => string;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Fail-closed JWT expiry probe (no signature verification — cryptographic
|
|
117
|
+
* validation belongs to the backend and the Cognito SDK; this is purely a
|
|
118
|
+
* render-gating heuristic so a dead token never passes a page-load guard).
|
|
119
|
+
*
|
|
120
|
+
* Returns true when the token is expired OR cannot be parsed / carries no
|
|
121
|
+
* numeric `exp` claim. Real Cognito id tokens always carry `exp`.
|
|
122
|
+
*/
|
|
123
|
+
export declare function isTokenExpired(token: string, nowMs?: number): boolean;
|
|
115
124
|
export declare class CognitoClient {
|
|
116
125
|
private readonly options;
|
|
117
126
|
private pool;
|
|
@@ -146,6 +155,19 @@ export declare class CognitoClient {
|
|
|
146
155
|
*/
|
|
147
156
|
completeNewPassword(newPassword: string, userAttributes?: Record<string, unknown>): Promise<SessionTokens>;
|
|
148
157
|
getSession(): Promise<RestoredSession | null>;
|
|
158
|
+
/**
|
|
159
|
+
* Canonical async page-load gate for protected pages.
|
|
160
|
+
*
|
|
161
|
+
* Unlike a sync token-presence check (which passes with a stale-but-cached
|
|
162
|
+
* token after credentials timeout, letting the page fetch and render
|
|
163
|
+
* private data before the 401 path discovers the dead session), this
|
|
164
|
+
* validates the session through the SDK: a live session is returned
|
|
165
|
+
* (refreshing via the stored refresh token when the id token expired but
|
|
166
|
+
* the refresh token is still alive — seamless, no redirect), while a truly
|
|
167
|
+
* dead session resolves null AND redirects to `loginUrl` immediately —
|
|
168
|
+
* before any API fetch fires. Never throws.
|
|
169
|
+
*/
|
|
170
|
+
ensureSession(loginUrl: string): Promise<RestoredSession | null>;
|
|
149
171
|
refreshSession(): Promise<SessionTokens>;
|
|
150
172
|
forgotPassword(email: string): Promise<void>;
|
|
151
173
|
confirmNewPassword(email: string, code: string, newPassword: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,30 @@
|
|
|
28
28
|
function resolvePoolConfig(value) {
|
|
29
29
|
return typeof value === 'function' ? value() : value;
|
|
30
30
|
}
|
|
31
|
+
/** Clock skew (seconds) tolerated when judging a JWT expired. */
|
|
32
|
+
const TOKEN_EXPIRY_SKEW_SEC = 60;
|
|
33
|
+
/**
|
|
34
|
+
* Fail-closed JWT expiry probe (no signature verification — cryptographic
|
|
35
|
+
* validation belongs to the backend and the Cognito SDK; this is purely a
|
|
36
|
+
* render-gating heuristic so a dead token never passes a page-load guard).
|
|
37
|
+
*
|
|
38
|
+
* Returns true when the token is expired OR cannot be parsed / carries no
|
|
39
|
+
* numeric `exp` claim. Real Cognito id tokens always carry `exp`.
|
|
40
|
+
*/
|
|
41
|
+
export function isTokenExpired(token, nowMs = Date.now()) {
|
|
42
|
+
try {
|
|
43
|
+
const parts = token.split('.');
|
|
44
|
+
if (parts.length < 2)
|
|
45
|
+
return true;
|
|
46
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
47
|
+
if (typeof payload.exp !== 'number' || !Number.isFinite(payload.exp))
|
|
48
|
+
return true;
|
|
49
|
+
return payload.exp * 1000 <= nowMs + TOKEN_EXPIRY_SKEW_SEC * 1000;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
31
55
|
export class CognitoClient {
|
|
32
56
|
constructor(options) {
|
|
33
57
|
this.options = options;
|
|
@@ -190,6 +214,30 @@ export class CognitoClient {
|
|
|
190
214
|
}
|
|
191
215
|
});
|
|
192
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Canonical async page-load gate for protected pages.
|
|
219
|
+
*
|
|
220
|
+
* Unlike a sync token-presence check (which passes with a stale-but-cached
|
|
221
|
+
* token after credentials timeout, letting the page fetch and render
|
|
222
|
+
* private data before the 401 path discovers the dead session), this
|
|
223
|
+
* validates the session through the SDK: a live session is returned
|
|
224
|
+
* (refreshing via the stored refresh token when the id token expired but
|
|
225
|
+
* the refresh token is still alive — seamless, no redirect), while a truly
|
|
226
|
+
* dead session resolves null AND redirects to `loginUrl` immediately —
|
|
227
|
+
* before any API fetch fires. Never throws.
|
|
228
|
+
*/
|
|
229
|
+
async ensureSession(loginUrl) {
|
|
230
|
+
try {
|
|
231
|
+
const session = await this.getSession();
|
|
232
|
+
if (!session)
|
|
233
|
+
this.redirectToLogin(loginUrl);
|
|
234
|
+
return session;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
this.redirectToLogin(loginUrl);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
193
241
|
refreshSession() {
|
|
194
242
|
this.initPool();
|
|
195
243
|
const cognitoUser = this.pool.getCurrentUser();
|
|
@@ -203,6 +251,10 @@ export class CognitoClient {
|
|
|
203
251
|
return new Promise((resolve, reject) => {
|
|
204
252
|
cognitoUser.getSession((err, session) => {
|
|
205
253
|
if (err || !session || !session.isValid()) {
|
|
254
|
+
// Terminal failure — same cleanup as getSession: a stale/invalid
|
|
255
|
+
// cached session must not leave token state behind.
|
|
256
|
+
cognitoUser.signOut();
|
|
257
|
+
this.clearTokens();
|
|
206
258
|
return reject(err ? this.options.errorMapper(err) : new Error('No valid cached session'));
|
|
207
259
|
}
|
|
208
260
|
this.setTokensFromSession(session, cognitoUser.getUsername());
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.1.0",
|
|
7
7
|
"description": "Generic, dependency-injected browser AWS Cognito client — sign-up, sign-in, session restore/refresh, NEW_PASSWORD_REQUIRED challenge, forgot/reset password",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"author": "Victor Salmon",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"bugs": {
|
|
16
16
|
"url": "https://github.com/victorsalmon/cognito-client/issues"
|
|
17
17
|
},
|
|
18
|
+
"packageManager": "pnpm@11.22.0",
|
|
18
19
|
"keywords": [
|
|
19
20
|
"aws",
|
|
20
21
|
"cognito",
|
|
@@ -37,9 +38,14 @@
|
|
|
37
38
|
"build": "tsc -p tsconfig.build.json",
|
|
38
39
|
"postinstall": "tsc -p tsconfig.build.json",
|
|
39
40
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "vitest run"
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:property": "vitest run property",
|
|
43
|
+
"test:mutation": "stryker run"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
46
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
47
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
48
|
+
"fast-check": "^4.9.0",
|
|
43
49
|
"jsdom": "^26.0.0",
|
|
44
50
|
"typescript": "^5.7.2",
|
|
45
51
|
"vitest": "^3.2.7"
|