@fnd-platform/cognito-auth 1.0.0-alpha.1 → 1.0.0-alpha.10
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/lib/client/auth-client.d.ts +151 -94
- package/lib/client/auth-client.d.ts.map +1 -1
- package/lib/client/auth-client.js +330 -209
- package/lib/client/auth-client.js.map +1 -1
- package/lib/client/errors.d.ts +45 -23
- package/lib/client/errors.d.ts.map +1 -1
- package/lib/client/errors.js +80 -38
- package/lib/client/errors.js.map +1 -1
- package/lib/client/index.js +8 -23
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/jwt.js +27 -27
- package/lib/remix/admin.server.js +44 -45
- package/lib/remix/index.d.ts +3 -11
- package/lib/remix/index.d.ts.map +1 -1
- package/lib/remix/index.js +19 -90
- package/lib/remix/index.js.map +1 -1
- package/lib/remix/session.server.d.ts +44 -33
- package/lib/remix/session.server.d.ts.map +1 -1
- package/lib/remix/session.server.js +157 -95
- package/lib/remix/session.server.js.map +1 -1
- package/lib/types.d.ts +140 -106
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +3 -3
- package/package.json +1 -1
package/lib/client/errors.d.ts
CHANGED
|
@@ -9,18 +9,7 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Error codes for authentication failures.
|
|
11
11
|
*/
|
|
12
|
-
export type AuthErrorCode =
|
|
13
|
-
| 'INVALID_CREDENTIALS'
|
|
14
|
-
| 'USER_NOT_FOUND'
|
|
15
|
-
| 'USER_NOT_CONFIRMED'
|
|
16
|
-
| 'CODE_MISMATCH'
|
|
17
|
-
| 'CODE_EXPIRED'
|
|
18
|
-
| 'TOKEN_EXPIRED'
|
|
19
|
-
| 'INVALID_TOKEN'
|
|
20
|
-
| 'USER_EXISTS'
|
|
21
|
-
| 'PASSWORD_POLICY'
|
|
22
|
-
| 'RATE_LIMITED'
|
|
23
|
-
| 'SERVICE_ERROR';
|
|
12
|
+
export type AuthErrorCode = 'INVALID_CREDENTIALS' | 'USER_NOT_FOUND' | 'USER_NOT_CONFIRMED' | 'NEW_PASSWORD_REQUIRED' | 'CODE_MISMATCH' | 'CODE_EXPIRED' | 'TOKEN_EXPIRED' | 'INVALID_TOKEN' | 'USER_EXISTS' | 'PASSWORD_POLICY' | 'RATE_LIMITED' | 'SERVICE_ERROR';
|
|
24
13
|
/**
|
|
25
14
|
* Authentication error with structured error code.
|
|
26
15
|
*
|
|
@@ -43,16 +32,49 @@ export type AuthErrorCode =
|
|
|
43
32
|
* ```
|
|
44
33
|
*/
|
|
45
34
|
export declare class AuthError extends Error {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
readonly code: AuthErrorCode;
|
|
36
|
+
readonly cause?: Error | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new AuthError.
|
|
39
|
+
*
|
|
40
|
+
* @param message - Human-readable error message
|
|
41
|
+
* @param code - Structured error code for programmatic handling
|
|
42
|
+
* @param cause - Original error that caused this error
|
|
43
|
+
*/
|
|
44
|
+
constructor(message: string, code: AuthErrorCode, cause?: Error | undefined);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when user must change their password before signing in.
|
|
48
|
+
*
|
|
49
|
+
* This occurs when:
|
|
50
|
+
* - User was created by admin and has FORCE_CHANGE_PASSWORD status
|
|
51
|
+
* - Password has expired per user pool policy
|
|
52
|
+
*
|
|
53
|
+
* The session token must be used to complete the password change via
|
|
54
|
+
* `authClient.completeNewPassword()`.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* try {
|
|
59
|
+
* await authClient.signIn(email, password);
|
|
60
|
+
* } catch (error) {
|
|
61
|
+
* if (error instanceof NewPasswordRequiredError) {
|
|
62
|
+
* // Redirect to change password page with session
|
|
63
|
+
* redirect(`/change-password?session=${error.session}&email=${email}`);
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare class NewPasswordRequiredError extends AuthError {
|
|
69
|
+
readonly session: string;
|
|
70
|
+
readonly email: string;
|
|
71
|
+
/**
|
|
72
|
+
* Creates a NewPasswordRequiredError.
|
|
73
|
+
*
|
|
74
|
+
* @param session - Cognito session token for completing password change
|
|
75
|
+
* @param email - User's email address
|
|
76
|
+
*/
|
|
77
|
+
constructor(session: string, email: string);
|
|
56
78
|
}
|
|
57
79
|
/**
|
|
58
80
|
* Maps a Cognito SDK error to an AuthError.
|
|
@@ -64,4 +86,4 @@ export declare class AuthError extends Error {
|
|
|
64
86
|
* @internal
|
|
65
87
|
*/
|
|
66
88
|
export declare function mapCognitoError(error: unknown, defaultMessage: string): AuthError;
|
|
67
|
-
//# sourceMappingURL=errors.d.ts.map
|
|
89
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,qBAAqB,GACrB,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,cAAc,GACd,eAAe,GACf,eAAe,GACf,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,eAAe,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,SAAU,SAAQ,KAAK;aAUhB,IAAI,EAAE,aAAa;aACnB,KAAK,CAAC,EAAE,KAAK;IAV/B;;;;;;OAMG;gBAED,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,aAAa,EACnB,KAAK,CAAC,EAAE,KAAK,YAAA;CAShC;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,CAOjF"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,qBAAqB,GACrB,gBAAgB,GAChB,oBAAoB,GACpB,uBAAuB,GACvB,eAAe,GACf,cAAc,GACd,eAAe,GACf,eAAe,GACf,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,eAAe,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,SAAU,SAAQ,KAAK;aAUhB,IAAI,EAAE,aAAa;aACnB,KAAK,CAAC,EAAE,KAAK;IAV/B;;;;;;OAMG;gBAED,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,aAAa,EACnB,KAAK,CAAC,EAAE,KAAK,YAAA;CAShC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;aAQnC,OAAO,EAAE,MAAM;aACf,KAAK,EAAE,MAAM;IAR/B;;;;;OAKG;gBAEe,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM;CAQhC;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,CAOjF"}
|
package/lib/client/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Authentication error types for FndAuthClient.
|
|
4
4
|
*
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
Object.defineProperty(exports,
|
|
11
|
-
exports.AuthError = void 0;
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.NewPasswordRequiredError = exports.AuthError = void 0;
|
|
12
12
|
exports.mapCognitoError = mapCognitoError;
|
|
13
13
|
/**
|
|
14
14
|
* Authentication error with structured error code.
|
|
@@ -32,43 +32,85 @@ exports.mapCognitoError = mapCognitoError;
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
class AuthError extends Error {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
code;
|
|
36
|
+
cause;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new AuthError.
|
|
39
|
+
*
|
|
40
|
+
* @param message - Human-readable error message
|
|
41
|
+
* @param code - Structured error code for programmatic handling
|
|
42
|
+
* @param cause - Original error that caused this error
|
|
43
|
+
*/
|
|
44
|
+
constructor(message, code, cause) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.code = code;
|
|
47
|
+
this.cause = cause;
|
|
48
|
+
this.name = 'AuthError';
|
|
49
|
+
// Maintains proper stack trace in V8 environments
|
|
50
|
+
if (Error.captureStackTrace) {
|
|
51
|
+
Error.captureStackTrace(this, AuthError);
|
|
52
|
+
}
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
54
|
}
|
|
55
55
|
exports.AuthError = AuthError;
|
|
56
|
+
/**
|
|
57
|
+
* Error thrown when user must change their password before signing in.
|
|
58
|
+
*
|
|
59
|
+
* This occurs when:
|
|
60
|
+
* - User was created by admin and has FORCE_CHANGE_PASSWORD status
|
|
61
|
+
* - Password has expired per user pool policy
|
|
62
|
+
*
|
|
63
|
+
* The session token must be used to complete the password change via
|
|
64
|
+
* `authClient.completeNewPassword()`.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* try {
|
|
69
|
+
* await authClient.signIn(email, password);
|
|
70
|
+
* } catch (error) {
|
|
71
|
+
* if (error instanceof NewPasswordRequiredError) {
|
|
72
|
+
* // Redirect to change password page with session
|
|
73
|
+
* redirect(`/change-password?session=${error.session}&email=${email}`);
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
class NewPasswordRequiredError extends AuthError {
|
|
79
|
+
session;
|
|
80
|
+
email;
|
|
81
|
+
/**
|
|
82
|
+
* Creates a NewPasswordRequiredError.
|
|
83
|
+
*
|
|
84
|
+
* @param session - Cognito session token for completing password change
|
|
85
|
+
* @param email - User's email address
|
|
86
|
+
*/
|
|
87
|
+
constructor(session, email) {
|
|
88
|
+
super('Password change required', 'NEW_PASSWORD_REQUIRED');
|
|
89
|
+
this.session = session;
|
|
90
|
+
this.email = email;
|
|
91
|
+
this.name = 'NewPasswordRequiredError';
|
|
92
|
+
if (Error.captureStackTrace) {
|
|
93
|
+
Error.captureStackTrace(this, NewPasswordRequiredError);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.NewPasswordRequiredError = NewPasswordRequiredError;
|
|
56
98
|
/**
|
|
57
99
|
* Maps Cognito exception names to AuthErrorCode.
|
|
58
100
|
*
|
|
59
101
|
* @internal
|
|
60
102
|
*/
|
|
61
103
|
const COGNITO_ERROR_MAP = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
NotAuthorizedException: 'INVALID_CREDENTIALS',
|
|
105
|
+
UserNotFoundException: 'USER_NOT_FOUND',
|
|
106
|
+
UserNotConfirmedException: 'USER_NOT_CONFIRMED',
|
|
107
|
+
CodeMismatchException: 'CODE_MISMATCH',
|
|
108
|
+
ExpiredCodeException: 'CODE_EXPIRED',
|
|
109
|
+
UsernameExistsException: 'USER_EXISTS',
|
|
110
|
+
InvalidPasswordException: 'PASSWORD_POLICY',
|
|
111
|
+
InvalidParameterException: 'PASSWORD_POLICY',
|
|
112
|
+
TooManyRequestsException: 'RATE_LIMITED',
|
|
113
|
+
LimitExceededException: 'RATE_LIMITED',
|
|
72
114
|
};
|
|
73
115
|
/**
|
|
74
116
|
* Maps a Cognito SDK error to an AuthError.
|
|
@@ -80,11 +122,11 @@ const COGNITO_ERROR_MAP = {
|
|
|
80
122
|
* @internal
|
|
81
123
|
*/
|
|
82
124
|
function mapCognitoError(error, defaultMessage) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
const errorName = error.name;
|
|
127
|
+
const code = COGNITO_ERROR_MAP[errorName] ?? 'SERVICE_ERROR';
|
|
128
|
+
return new AuthError(error.message || defaultMessage, code, error);
|
|
129
|
+
}
|
|
130
|
+
return new AuthError(defaultMessage, 'SERVICE_ERROR');
|
|
89
131
|
}
|
|
90
|
-
//# sourceMappingURL=errors.js.map
|
|
132
|
+
//# sourceMappingURL=errors.js.map
|
package/lib/client/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAkIH,0CAOC;AAtHD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,SAAU,SAAQ,KAAK;IAUhB;IACA;IAVlB;;;;;;OAMG;IACH,YACE,OAAe,EACC,IAAmB,EACnB,KAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAe;QACnB,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,kDAAkD;QAClD,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC5B,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF;AApBD,8BAoBC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,wBAAyB,SAAQ,SAAS;IAQnC;IACA;IARlB;;;;;OAKG;IACH,YACkB,OAAe,EACf,KAAa;QAE7B,KAAK,CAAC,0BAA0B,EAAE,uBAAuB,CAAC,CAAC;QAH3C,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC5B,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;CACF;AAjBD,4DAiBC;AAED;;;;GAIG;AACH,MAAM,iBAAiB,GAAkC;IACvD,sBAAsB,EAAE,qBAAqB;IAC7C,qBAAqB,EAAE,gBAAgB;IACvC,yBAAyB,EAAE,oBAAoB;IAC/C,qBAAqB,EAAE,eAAe;IACtC,oBAAoB,EAAE,cAAc;IACpC,uBAAuB,EAAE,aAAa;IACtC,wBAAwB,EAAE,iBAAiB;IAC3C,yBAAyB,EAAE,iBAAiB;IAC5C,wBAAwB,EAAE,cAAc;IACxC,sBAAsB,EAAE,cAAc;CACvC,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAAC,KAAc,EAAE,cAAsB;IACpE,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC;QAC7B,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,eAAe,CAAC;QAC7D,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,IAAI,cAAc,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,IAAI,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;AACxD,CAAC"}
|
package/lib/client/index.js
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Auth client exports.
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
Object.defineProperty(exports,
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.AuthError = exports.clearClientCache = exports.FndAuthClient = void 0;
|
|
9
|
-
var auth_client_js_1 = require(
|
|
10
|
-
Object.defineProperty(exports,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
16
|
-
Object.defineProperty(exports, 'clearClientCache', {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
get: function () {
|
|
19
|
-
return auth_client_js_1.clearClientCache;
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
var errors_js_1 = require('./errors.js');
|
|
23
|
-
Object.defineProperty(exports, 'AuthError', {
|
|
24
|
-
enumerable: true,
|
|
25
|
-
get: function () {
|
|
26
|
-
return errors_js_1.AuthError;
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
//# sourceMappingURL=index.js.map
|
|
9
|
+
var auth_client_js_1 = require("./auth-client.js");
|
|
10
|
+
Object.defineProperty(exports, "FndAuthClient", { enumerable: true, get: function () { return auth_client_js_1.FndAuthClient; } });
|
|
11
|
+
Object.defineProperty(exports, "clearClientCache", { enumerable: true, get: function () { return auth_client_js_1.clearClientCache; } });
|
|
12
|
+
var errors_js_1 = require("./errors.js");
|
|
13
|
+
Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_js_1.AuthError; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -22,9 +22,10 @@ export { handler as authorizerHandler } from './authorizer/handler.js';
|
|
|
22
22
|
export { refreshAccessToken, clearClientCache } from './utils/token-refresh.js';
|
|
23
23
|
export type { TokenRefreshConfig, RefreshResult } from './utils/token-refresh.js';
|
|
24
24
|
export { FndAuthClient, clearClientCache as clearAuthClientCache } from './client/auth-client.js';
|
|
25
|
-
export { AuthError } from './client/errors.js';
|
|
25
|
+
export { AuthError, NewPasswordRequiredError } from './client/errors.js';
|
|
26
26
|
export type { AuthErrorCode } from './client/errors.js';
|
|
27
|
-
export { createSessionStorage, getSession, createUserSession, requireAuth, getOptionalUser, getUserSession, logout, } from './remix/session.server.js';
|
|
27
|
+
export { createSessionStorage, getSession, createUserSession, requireAuth, getOptionalUser, getUserSession, getAccessToken, logout, } from './remix/session.server.js';
|
|
28
|
+
export type { GetAccessTokenConfig } from './remix/session.server.js';
|
|
28
29
|
export { requireAdmin, requireRole, hasRole, hasAnyRole } from './remix/admin.server.js';
|
|
29
|
-
export type { AuthClientConfig, AuthTokens, SignUpResult, SessionData, SessionUser, } from './types.js';
|
|
30
|
+
export type { AuthClientConfig, AuthTokens, SignUpResult, ForgotPasswordResult, SessionData, SessionUser, } from './types.js';
|
|
30
31
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGrE,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAG1F,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,yBAAyB,EACzB,UAAU,IAAI,iBAAiB,EAC/B,iBAAiB,IAAI,wBAAwB,GAC9C,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGvE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAChF,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EAAE,aAAa,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGrE,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAG1F,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,yBAAyB,EACzB,UAAU,IAAI,iBAAiB,EAC/B,iBAAiB,IAAI,wBAAwB,GAC9C,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGvE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAChF,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EAAE,aAAa,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,MAAM,GACP,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGzF,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @packageDocumentation
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.hasAnyRole = exports.hasRole = exports.requireRole = exports.requireAdmin = exports.logout = exports.getUserSession = exports.getOptionalUser = exports.requireAuth = exports.createUserSession = exports.getSession = exports.createSessionStorage = exports.AuthError = exports.clearAuthClientCache = exports.FndAuthClient = exports.clearClientCache = exports.refreshAccessToken = exports.authorizerHandler = exports.withCognitoAuth = exports.clearVerifierCache = exports.getVerifier = exports.verifyAndExtract = exports.verifyToken = exports.VALID_STAGES = exports.validateStage = exports.FndCognitoAuth = void 0;
|
|
16
|
+
exports.hasAnyRole = exports.hasRole = exports.requireRole = exports.requireAdmin = exports.logout = exports.getAccessToken = exports.getUserSession = exports.getOptionalUser = exports.requireAuth = exports.createUserSession = exports.getSession = exports.createSessionStorage = exports.NewPasswordRequiredError = exports.AuthError = exports.clearAuthClientCache = exports.FndAuthClient = exports.clearClientCache = exports.refreshAccessToken = exports.authorizerHandler = exports.withCognitoAuth = exports.clearVerifierCache = exports.getVerifier = exports.verifyAndExtract = exports.verifyToken = exports.VALID_STAGES = exports.validateStage = exports.FndCognitoAuth = void 0;
|
|
17
17
|
// ===== CDK Constructs =====
|
|
18
18
|
var cognito_construct_js_1 = require("./cognito-construct.js");
|
|
19
19
|
Object.defineProperty(exports, "FndCognitoAuth", { enumerable: true, get: function () { return cognito_construct_js_1.FndCognitoAuth; } });
|
|
@@ -42,6 +42,7 @@ Object.defineProperty(exports, "FndAuthClient", { enumerable: true, get: functio
|
|
|
42
42
|
Object.defineProperty(exports, "clearAuthClientCache", { enumerable: true, get: function () { return auth_client_js_1.clearClientCache; } });
|
|
43
43
|
var errors_js_1 = require("./client/errors.js");
|
|
44
44
|
Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_js_1.AuthError; } });
|
|
45
|
+
Object.defineProperty(exports, "NewPasswordRequiredError", { enumerable: true, get: function () { return errors_js_1.NewPasswordRequiredError; } });
|
|
45
46
|
// ===== Remix Utilities =====
|
|
46
47
|
var session_server_js_1 = require("./remix/session.server.js");
|
|
47
48
|
Object.defineProperty(exports, "createSessionStorage", { enumerable: true, get: function () { return session_server_js_1.createSessionStorage; } });
|
|
@@ -50,6 +51,7 @@ Object.defineProperty(exports, "createUserSession", { enumerable: true, get: fun
|
|
|
50
51
|
Object.defineProperty(exports, "requireAuth", { enumerable: true, get: function () { return session_server_js_1.requireAuth; } });
|
|
51
52
|
Object.defineProperty(exports, "getOptionalUser", { enumerable: true, get: function () { return session_server_js_1.getOptionalUser; } });
|
|
52
53
|
Object.defineProperty(exports, "getUserSession", { enumerable: true, get: function () { return session_server_js_1.getUserSession; } });
|
|
54
|
+
Object.defineProperty(exports, "getAccessToken", { enumerable: true, get: function () { return session_server_js_1.getAccessToken; } });
|
|
53
55
|
Object.defineProperty(exports, "logout", { enumerable: true, get: function () { return session_server_js_1.logout; } });
|
|
54
56
|
var admin_server_js_1 = require("./remix/admin.server.js");
|
|
55
57
|
Object.defineProperty(exports, "requireAdmin", { enumerable: true, get: function () { return admin_server_js_1.requireAdmin; } });
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,6BAA6B;AAC7B,+DAAwD;AAA/C,sHAAA,cAAc,OAAA;AAEvB,+DAAqE;AAA5D,qHAAA,aAAa,OAAA;AAAE,oHAAA,YAAY,OAAA;AAWpC,4BAA4B;AAC5B,mCAA0F;AAAjF,qGAAA,WAAW,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAEvE,yBAAyB;AACzB,gDAAuD;AAA9C,0GAAA,eAAe,OAAA;AAOxB,gCAAgC;AAChC,sDAAuE;AAA9D,+GAAA,OAAO,OAAqB;AAErC,8BAA8B;AAC9B,6DAAgF;AAAvE,sHAAA,kBAAkB,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAG7C,0BAA0B;AAC1B,0DAAkG;AAAzF,+GAAA,aAAa,OAAA;AAAE,sHAAA,gBAAgB,OAAwB;AAChE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,6BAA6B;AAC7B,+DAAwD;AAA/C,sHAAA,cAAc,OAAA;AAEvB,+DAAqE;AAA5D,qHAAA,aAAa,OAAA;AAAE,oHAAA,YAAY,OAAA;AAWpC,4BAA4B;AAC5B,mCAA0F;AAAjF,qGAAA,WAAW,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAEvE,yBAAyB;AACzB,gDAAuD;AAA9C,0GAAA,eAAe,OAAA;AAOxB,gCAAgC;AAChC,sDAAuE;AAA9D,+GAAA,OAAO,OAAqB;AAErC,8BAA8B;AAC9B,6DAAgF;AAAvE,sHAAA,kBAAkB,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAG7C,0BAA0B;AAC1B,0DAAkG;AAAzF,+GAAA,aAAa,OAAA;AAAE,sHAAA,gBAAgB,OAAwB;AAChE,gDAAyE;AAAhE,sGAAA,SAAS,OAAA;AAAE,qHAAA,wBAAwB,OAAA;AAG5C,8BAA8B;AAC9B,+DASmC;AARjC,yHAAA,oBAAoB,OAAA;AACpB,+GAAA,UAAU,OAAA;AACV,sHAAA,iBAAiB,OAAA;AACjB,gHAAA,WAAW,OAAA;AACX,oHAAA,eAAe,OAAA;AACf,mHAAA,cAAc,OAAA;AACd,mHAAA,cAAc,OAAA;AACd,2GAAA,MAAM,OAAA;AAKR,2DAAyF;AAAhF,+GAAA,YAAY,OAAA;AAAE,8GAAA,WAAW,OAAA;AAAE,0GAAA,OAAO,OAAA;AAAE,6GAAA,UAAU,OAAA"}
|
package/lib/jwt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* JWT verification utilities using aws-jwt-verify.
|
|
4
4
|
*
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
Object.defineProperty(exports,
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.getVerifier = getVerifier;
|
|
12
12
|
exports.verifyToken = verifyToken;
|
|
13
13
|
exports.verifyAndExtract = verifyAndExtract;
|
|
14
14
|
exports.clearVerifierCache = clearVerifierCache;
|
|
15
|
-
const aws_jwt_verify_1 = require(
|
|
15
|
+
const aws_jwt_verify_1 = require("aws-jwt-verify");
|
|
16
16
|
/** Verifier cache keyed by userPoolId+clientId+tokenUse */
|
|
17
17
|
const verifierCache = new Map();
|
|
18
18
|
/**
|
|
@@ -22,7 +22,7 @@ const verifierCache = new Map();
|
|
|
22
22
|
* @returns Cache key string
|
|
23
23
|
*/
|
|
24
24
|
function getCacheKey(config) {
|
|
25
|
-
|
|
25
|
+
return `${config.userPoolId}:${config.clientId}:${config.tokenUse ?? 'access'}`;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Gets or creates a verifier for the given configuration.
|
|
@@ -40,16 +40,16 @@ function getCacheKey(config) {
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
function getVerifier(config) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
const key = getCacheKey(config);
|
|
44
|
+
if (!verifierCache.has(key)) {
|
|
45
|
+
const verifier = aws_jwt_verify_1.CognitoJwtVerifier.create({
|
|
46
|
+
userPoolId: config.userPoolId,
|
|
47
|
+
clientId: config.clientId,
|
|
48
|
+
tokenUse: config.tokenUse ?? 'access',
|
|
49
|
+
});
|
|
50
|
+
verifierCache.set(key, verifier);
|
|
51
|
+
}
|
|
52
|
+
return verifierCache.get(key);
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* Verifies a JWT token from Cognito.
|
|
@@ -69,10 +69,10 @@ function getVerifier(config) {
|
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
71
|
async function verifyToken(token, config) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const verifier = getVerifier(config);
|
|
73
|
+
const payload = await verifier.verify(token);
|
|
74
|
+
// Cast through unknown to handle aws-jwt-verify's generic payload type
|
|
75
|
+
return payload;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* Verifies a token and returns a normalized result.
|
|
@@ -93,13 +93,13 @@ async function verifyToken(token, config) {
|
|
|
93
93
|
* ```
|
|
94
94
|
*/
|
|
95
95
|
async function verifyAndExtract(token, config) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
const payload = await verifyToken(token, config);
|
|
97
|
+
return {
|
|
98
|
+
userId: payload.sub,
|
|
99
|
+
email: 'email' in payload ? payload.email : undefined,
|
|
100
|
+
groups: payload['cognito:groups'] ?? [],
|
|
101
|
+
payload,
|
|
102
|
+
};
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Clears the verifier cache. Useful for testing.
|
|
@@ -112,6 +112,6 @@ async function verifyAndExtract(token, config) {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
function clearVerifierCache() {
|
|
115
|
-
|
|
115
|
+
verifierCache.clear();
|
|
116
116
|
}
|
|
117
|
-
//# sourceMappingURL=jwt.js.map
|
|
117
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Admin and role-based access utilities for Remix.
|
|
4
4
|
*
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
-
Object.defineProperty(exports,
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.requireAdmin = requireAdmin;
|
|
11
11
|
exports.requireRole = requireRole;
|
|
12
12
|
exports.hasRole = hasRole;
|
|
13
13
|
exports.hasAnyRole = hasAnyRole;
|
|
14
|
-
const node_1 = require(
|
|
15
|
-
const session_server_js_1 = require(
|
|
14
|
+
const node_1 = require("@remix-run/node");
|
|
15
|
+
const session_server_js_1 = require("./session.server.js");
|
|
16
16
|
/**
|
|
17
17
|
* Requires admin role for accessing a route.
|
|
18
18
|
*
|
|
@@ -35,7 +35,7 @@ const session_server_js_1 = require('./session.server.js');
|
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
async function requireAdmin(request, storage) {
|
|
38
|
-
|
|
38
|
+
return requireRole(request, ['admin'], storage);
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Requires any of the specified roles for accessing a route.
|
|
@@ -60,25 +60,22 @@ async function requireAdmin(request, storage) {
|
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
62
|
async function requireRole(request, roles, storage) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return userId;
|
|
63
|
+
// First ensure user is authenticated
|
|
64
|
+
const userId = await (0, session_server_js_1.requireAuth)(request, '/login', storage);
|
|
65
|
+
// Get user's groups from session
|
|
66
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
67
|
+
const userGroups = session.get('groups') ?? [];
|
|
68
|
+
// Check if user has at least one required role
|
|
69
|
+
const hasRequiredRole = roles.some((role) => userGroups.includes(role));
|
|
70
|
+
if (!hasRequiredRole) {
|
|
71
|
+
throw (0, node_1.json)({
|
|
72
|
+
error: {
|
|
73
|
+
code: 'FORBIDDEN',
|
|
74
|
+
message: `Required role: ${roles.join(' or ')}`,
|
|
75
|
+
},
|
|
76
|
+
}, { status: 403 });
|
|
77
|
+
}
|
|
78
|
+
return userId;
|
|
82
79
|
}
|
|
83
80
|
/**
|
|
84
81
|
* Checks if the user has a specific role.
|
|
@@ -100,17 +97,18 @@ async function requireRole(request, roles, storage) {
|
|
|
100
97
|
* ```
|
|
101
98
|
*/
|
|
102
99
|
async function hasRole(request, role, storage) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
try {
|
|
101
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
102
|
+
const userId = session.get('userId');
|
|
103
|
+
if (!userId) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const userGroups = session.get('groups') ?? [];
|
|
107
|
+
return userGroups.includes(role);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
108
111
|
}
|
|
109
|
-
const userGroups = session.get('groups') ?? [];
|
|
110
|
-
return userGroups.includes(role);
|
|
111
|
-
} catch {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
112
|
}
|
|
115
113
|
/**
|
|
116
114
|
* Checks if the user has any of the specified roles.
|
|
@@ -131,16 +129,17 @@ async function hasRole(request, role, storage) {
|
|
|
131
129
|
* ```
|
|
132
130
|
*/
|
|
133
131
|
async function hasAnyRole(request, roles, storage) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
try {
|
|
133
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
134
|
+
const userId = session.get('userId');
|
|
135
|
+
if (!userId) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const userGroups = session.get('groups') ?? [];
|
|
139
|
+
return roles.some((role) => userGroups.includes(role));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
139
143
|
}
|
|
140
|
-
const userGroups = session.get('groups') ?? [];
|
|
141
|
-
return roles.some((role) => userGroups.includes(role));
|
|
142
|
-
} catch {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
144
|
}
|
|
146
|
-
//# sourceMappingURL=admin.server.js.map
|
|
145
|
+
//# sourceMappingURL=admin.server.js.map
|
package/lib/remix/index.d.ts
CHANGED
|
@@ -3,15 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
7
|
-
|
|
8
|
-
getSession,
|
|
9
|
-
createUserSession,
|
|
10
|
-
requireAuth,
|
|
11
|
-
getOptionalUser,
|
|
12
|
-
getUserSession,
|
|
13
|
-
logout,
|
|
14
|
-
resetDefaultStorage,
|
|
15
|
-
} from './session.server.js';
|
|
6
|
+
export { createSessionStorage, getSession, createUserSession, requireAuth, getOptionalUser, getUserSession, getAccessToken, logout, resetDefaultStorage, } from './session.server.js';
|
|
7
|
+
export type { GetAccessTokenConfig } from './session.server.js';
|
|
16
8
|
export { requireAdmin, requireRole, hasRole, hasAnyRole } from './admin.server.js';
|
|
17
|
-
//# sourceMappingURL=index.d.ts.map
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
package/lib/remix/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/remix/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,cAAc,EACd,MAAM,EACN,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/remix/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,MAAM,EACN,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|