tsledge 0.1.9 → 0.1.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/dist/index.js +29 -26
- package/dist/middleware/authentication/session.d.ts +1 -1
- package/dist/middleware/authentication/session.d.ts.map +1 -1
- package/dist/middleware/authentication/validation.d.ts.map +1 -1
- package/dist/models/auth-token-blocklist.d.ts +13 -0
- package/dist/models/auth-token-blocklist.d.ts.map +1 -0
- package/dist/models/auth-user.d.ts +4 -3
- package/dist/models/auth-user.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.d.ts.map +1 -1
- package/package.json +9 -6
- package/dist/models/token-blocklist.d.ts +0 -12
- package/dist/models/token-blocklist.d.ts.map +0 -1
- package/dist/src/index.js +0 -1024
- package/dist/tests/main.js +0 -965
package/dist/index.js
CHANGED
|
@@ -165,6 +165,11 @@ function errorLogger(err, req, res, next) {
|
|
|
165
165
|
|
|
166
166
|
// src/middleware/authentication/session.ts
|
|
167
167
|
import express from "express";
|
|
168
|
+
import bcrypt from "bcrypt";
|
|
169
|
+
import jwt2 from "jsonwebtoken";
|
|
170
|
+
|
|
171
|
+
// src/middleware/authentication/validation.ts
|
|
172
|
+
import jwt from "jsonwebtoken";
|
|
168
173
|
|
|
169
174
|
// src/models/auth-user.ts
|
|
170
175
|
import mongoose2 from "mongoose";
|
|
@@ -176,24 +181,22 @@ var AuthUserSchema = new mongoose2.Schema(
|
|
|
176
181
|
},
|
|
177
182
|
{ collection: "auth_users", timestamps: true }
|
|
178
183
|
);
|
|
179
|
-
var
|
|
184
|
+
var AuthUserModel = mongoose2.model("AuthUser", AuthUserSchema);
|
|
180
185
|
|
|
181
|
-
// src/models/token-blocklist.ts
|
|
186
|
+
// src/models/auth-token-blocklist.ts
|
|
182
187
|
import mongoose3 from "mongoose";
|
|
183
|
-
var
|
|
188
|
+
var AuthTokenBlocklistSchema = new mongoose3.Schema(
|
|
184
189
|
{
|
|
185
190
|
jti: { type: String, required: true }
|
|
186
191
|
},
|
|
187
192
|
{ collection: "token_blocklist", timestamps: true }
|
|
188
193
|
);
|
|
189
|
-
var
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
import jwt2 from "jsonwebtoken";
|
|
194
|
+
var AuthTokenBlocklistModel = mongoose3.model(
|
|
195
|
+
"AuthTokenBlocklist",
|
|
196
|
+
AuthTokenBlocklistSchema
|
|
197
|
+
);
|
|
194
198
|
|
|
195
199
|
// src/middleware/authentication/validation.ts
|
|
196
|
-
import jwt from "jsonwebtoken";
|
|
197
200
|
var FORBIDDEN = 403;
|
|
198
201
|
var UNAUTHORIZED = 401;
|
|
199
202
|
async function jwtRequired(req, res, next) {
|
|
@@ -210,14 +213,14 @@ async function verifyToken(token, jwtSecret) {
|
|
|
210
213
|
console.log("[WARN] JWT token without jti");
|
|
211
214
|
return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
|
|
212
215
|
}
|
|
213
|
-
const existingBlock = await
|
|
216
|
+
const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
|
|
214
217
|
if (existingBlock) {
|
|
215
218
|
console.log("[WARN] JWT token is blocked");
|
|
216
219
|
return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
|
|
217
220
|
}
|
|
218
221
|
const identifier = payload.identifier;
|
|
219
222
|
if (identifier) {
|
|
220
|
-
const user = await
|
|
223
|
+
const user = await AuthUserModel.findOne({ identifier });
|
|
221
224
|
if (!user) {
|
|
222
225
|
console.log("[WARN] JWT token for non-existing user");
|
|
223
226
|
return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
|
|
@@ -309,9 +312,9 @@ async function generateCredentials(auth) {
|
|
|
309
312
|
let blocked = void 0;
|
|
310
313
|
do {
|
|
311
314
|
jti = crypto.randomUUID();
|
|
312
|
-
blocked = await
|
|
315
|
+
blocked = await AuthTokenBlocklistModel.findOne({ jti });
|
|
313
316
|
} while (blocked != void 0);
|
|
314
|
-
const user = await
|
|
317
|
+
const user = await AuthUserModel.findOne({ identifier: auth.identifier }).lean();
|
|
315
318
|
if (!user) {
|
|
316
319
|
return void 0;
|
|
317
320
|
}
|
|
@@ -341,11 +344,11 @@ async function authRegister(req, res, next) {
|
|
|
341
344
|
return res.sendStatus(FORBIDDEN2);
|
|
342
345
|
}
|
|
343
346
|
identifier = identifier.toLowerCase();
|
|
344
|
-
let user = await
|
|
347
|
+
let user = await AuthUserModel.findOne({ identifier });
|
|
345
348
|
if (user) {
|
|
346
349
|
return res.sendStatus(BAD_REQUEST);
|
|
347
350
|
}
|
|
348
|
-
res.locals.authUser = new
|
|
351
|
+
res.locals.authUser = new AuthUserModel({
|
|
349
352
|
identifier,
|
|
350
353
|
secretHash: await bcrypt.hash(secret, 10)
|
|
351
354
|
});
|
|
@@ -357,7 +360,7 @@ async function authLogin(req, res, next) {
|
|
|
357
360
|
return res.sendStatus(FORBIDDEN2);
|
|
358
361
|
}
|
|
359
362
|
identifier = identifier.toLowerCase();
|
|
360
|
-
let user = await
|
|
363
|
+
let user = await AuthUserModel.findOne({ identifier }).select("+secretHash");
|
|
361
364
|
if (!user || !user.secretHash) {
|
|
362
365
|
return res.sendStatus(BAD_REQUEST);
|
|
363
366
|
}
|
|
@@ -384,9 +387,9 @@ async function authLogout(req, res, next) {
|
|
|
384
387
|
const decoded = jwt2.decode(refreshToken);
|
|
385
388
|
const jti = decoded?.jti;
|
|
386
389
|
if (jti) {
|
|
387
|
-
const existingBlock = await
|
|
390
|
+
const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
|
|
388
391
|
if (!existingBlock) {
|
|
389
|
-
await new
|
|
392
|
+
await new AuthTokenBlocklistModel({ jti }).save();
|
|
390
393
|
}
|
|
391
394
|
}
|
|
392
395
|
let accessToken = validateString(req.body?.access_token);
|
|
@@ -394,9 +397,9 @@ async function authLogout(req, res, next) {
|
|
|
394
397
|
const accessTokenDecoded = jwt2.decode(accessToken);
|
|
395
398
|
let accessTokenJti = accessTokenDecoded?.jti;
|
|
396
399
|
if (accessTokenJti) {
|
|
397
|
-
const existing = await
|
|
400
|
+
const existing = await AuthTokenBlocklistModel.findOne({ jti: accessTokenJti });
|
|
398
401
|
if (!existing) {
|
|
399
|
-
await new
|
|
402
|
+
await new AuthTokenBlocklistModel({ jti: accessTokenJti }).save();
|
|
400
403
|
}
|
|
401
404
|
}
|
|
402
405
|
}
|
|
@@ -414,9 +417,9 @@ async function authRefresh(req, res, next) {
|
|
|
414
417
|
const decoded = jwt2.decode(refreshToken);
|
|
415
418
|
const jti = decoded?.jti;
|
|
416
419
|
if (jti) {
|
|
417
|
-
const existingBlock = await
|
|
420
|
+
const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
|
|
418
421
|
if (!existingBlock) {
|
|
419
|
-
await new
|
|
422
|
+
await new AuthTokenBlocklistModel({ jti }).save();
|
|
420
423
|
}
|
|
421
424
|
}
|
|
422
425
|
let accessToken = validateString(req.body?.access_token);
|
|
@@ -424,11 +427,11 @@ async function authRefresh(req, res, next) {
|
|
|
424
427
|
const accessTokenDecoded = jwt2.decode(accessToken);
|
|
425
428
|
let accessTokenJti = accessTokenDecoded?.jti;
|
|
426
429
|
if (accessTokenJti) {
|
|
427
|
-
const existing = await
|
|
430
|
+
const existing = await AuthTokenBlocklistModel.findOne({
|
|
428
431
|
jti: accessTokenJti
|
|
429
432
|
});
|
|
430
433
|
if (!existing) {
|
|
431
|
-
await new
|
|
434
|
+
await new AuthTokenBlocklistModel({ jti: accessTokenJti }).save();
|
|
432
435
|
}
|
|
433
436
|
}
|
|
434
437
|
}
|
|
@@ -985,7 +988,8 @@ function createApp() {
|
|
|
985
988
|
return app;
|
|
986
989
|
}
|
|
987
990
|
export {
|
|
988
|
-
|
|
991
|
+
AuthTokenBlocklistModel,
|
|
992
|
+
AuthUserModel,
|
|
989
993
|
Codec,
|
|
990
994
|
EXIT_CODE_GENERAL_ERROR,
|
|
991
995
|
EXIT_CODE_INVALID_CONFIG,
|
|
@@ -996,7 +1000,6 @@ export {
|
|
|
996
1000
|
JwtRefreshSecret,
|
|
997
1001
|
JwtSecret,
|
|
998
1002
|
QueryBuilder,
|
|
999
|
-
TokenBlocklist,
|
|
1000
1003
|
authLogin,
|
|
1001
1004
|
authLogout,
|
|
1002
1005
|
authRefresh,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express, { Request, Response } from 'express';
|
|
2
|
-
import { AuthUserDocument } from '../../models';
|
|
3
2
|
import { JWTCredentials, AuthUserPayload } from './types';
|
|
3
|
+
import { AuthUserDocument } from '../../models';
|
|
4
4
|
declare const router: import("express-serve-static-core").Router;
|
|
5
5
|
/**
|
|
6
6
|
* Handles user registration by validating input and creating a new user with a hashed password.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/middleware/authentication/session.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/middleware/authentication/session.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1D,OAAO,EAAE,gBAAgB,EAA0C,MAAM,cAAc,CAAC;AAExF,QAAA,MAAM,MAAM,4CAAmB,CAAC;AA2ChC;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAA;KAAE,CAAA;CAAE,EAC1D,IAAI,EAAE,GAAG;YADiB;QAAE,QAAQ,EAAE,gBAAgB,CAAA;KAAE;gBAkBzD;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,WAAW,EAAE,cAAc,CAAA;KAAE,CAAA;CAAE,EAAE,IAAI,EAAE,GAAG;YAA5C;QAAE,WAAW,EAAE,cAAc,CAAA;KAAE;gBAuBtG;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACpE,IAAI,EAAE,GAAG,iBA4BV;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,cAAc,CAAA;KAAE,CAAA;CAAE,EACjG,IAAI,EAAE,GAAG;YADiB;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,cAAc,CAAA;KAAE;gBAyChG;AAED,eAAe,MAAM,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/middleware/authentication/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/middleware/authentication/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,eAAe,GAAG,GAAG,CAAC;CAChC;AAKD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACpE,IAAI,EAAE,GAAG;YAuFwD;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;gBApF1G;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACpE,IAAI,EAAE,GAAG;YAuEwD;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;gBApE1G;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgDpG;AAwCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,gBAqBzD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
export interface AuthTokenBlocklist {
|
|
3
|
+
jti: string;
|
|
4
|
+
}
|
|
5
|
+
export type AuthTokenBlocklistDocument = AuthTokenBlocklist & mongoose.Document;
|
|
6
|
+
export declare const AuthTokenBlocklistModel: mongoose.Model<AuthTokenBlocklistDocument, {}, {}, {}, mongoose.Document<unknown, {}, AuthTokenBlocklistDocument, {}, mongoose.DefaultSchemaOptions> & AuthTokenBlocklist & mongoose.Document<mongoose.Types.ObjectId, any, any, Record<string, any>, {}> & Required<{
|
|
7
|
+
_id: mongoose.Types.ObjectId;
|
|
8
|
+
}> & {
|
|
9
|
+
__v: number;
|
|
10
|
+
} & {
|
|
11
|
+
id: string;
|
|
12
|
+
}, any, AuthTokenBlocklistDocument>;
|
|
13
|
+
//# sourceMappingURL=auth-token-blocklist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-token-blocklist.d.ts","sourceRoot":"","sources":["../../src/models/auth-token-blocklist.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,0BAA0B,GAAG,kBAAkB,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAShF,eAAO,MAAM,uBAAuB;;;;;;mCAGnC,CAAC"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import mongoose
|
|
2
|
-
export interface
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
export interface AuthUser {
|
|
3
3
|
identifier: string;
|
|
4
4
|
secretHash: string;
|
|
5
5
|
blockedSince: Date;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export type AuthUserDocument = AuthUser & mongoose.Document;
|
|
8
|
+
export declare const AuthUserModel: mongoose.Model<AuthUserDocument, {}, {}, {}, mongoose.Document<unknown, {}, AuthUserDocument, {}, mongoose.DefaultSchemaOptions> & AuthUser & mongoose.Document<mongoose.Types.ObjectId, any, any, Record<string, any>, {}> & Required<{
|
|
8
9
|
_id: mongoose.Types.ObjectId;
|
|
9
10
|
}> & {
|
|
10
11
|
__v: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-user.d.ts","sourceRoot":"","sources":["../../src/models/auth-user.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"auth-user.d.ts","sourceRoot":"","sources":["../../src/models/auth-user.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAW5D,eAAO,MAAM,aAAa;;;;;;yBAA+D,CAAC"}
|
package/dist/models/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Niklas Wockenfuß",
|
|
3
3
|
"homepage": "https://niklaswockenfuss.de/",
|
|
4
4
|
"name": "tsledge",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.10",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"description": "My playground and some helpful tools for web development ",
|
|
8
8
|
"main": "dist/index.js",
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
"node": ">=23.0.0"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"esbuild:build:watch": "node esbuild.js --watch",
|
|
21
|
-
"esbuild:dev:watch": "node --watch dist/tests/main.js",
|
|
22
|
-
"build": "node esbuild.js && tsc --emitDeclarationOnly",
|
|
20
|
+
"esbuild:build:watch": "node ./esbuild.js --watch",
|
|
21
|
+
"esbuild:dev:watch": "node --watch ./dist/tests/main.js",
|
|
22
|
+
"build": "npm i && node esbuild.js && tsc --emitDeclarationOnly",
|
|
23
23
|
"dev": "concurrently \"npm run esbuild:build:watch\" \"npm run esbuild:dev:watch\"",
|
|
24
24
|
"repl": "tsx ./scripts/repl.ts",
|
|
25
|
-
"npmjs": "npm run build && npm publish"
|
|
25
|
+
"npmjs": "npm run build && npm publish",
|
|
26
|
+
"package": "npm run build && npm pack"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"bcrypt": "^6.0.0",
|
|
@@ -30,9 +31,11 @@
|
|
|
30
31
|
"dotenv": "^17.2.3",
|
|
31
32
|
"express": "^5.2.1",
|
|
32
33
|
"jsonwebtoken": "^9.0.3",
|
|
33
|
-
"mongoose": "^9.1.5",
|
|
34
34
|
"multer": "^2.0.2"
|
|
35
35
|
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"mongoose": "9.2.1"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@types/bcrypt": "^6.0.0",
|
|
38
41
|
"@types/cors": "^2.8.19",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
2
|
-
export interface TokenBlocklistDocument extends Document {
|
|
3
|
-
jti: string;
|
|
4
|
-
}
|
|
5
|
-
export declare const TokenBlocklist: mongoose.Model<TokenBlocklistDocument, {}, {}, {}, mongoose.Document<unknown, {}, TokenBlocklistDocument, {}, mongoose.DefaultSchemaOptions> & TokenBlocklistDocument & {
|
|
6
|
-
_id: mongoose.Types.ObjectId;
|
|
7
|
-
} & {
|
|
8
|
-
__v: number;
|
|
9
|
-
} & {
|
|
10
|
-
id: string;
|
|
11
|
-
}, any, TokenBlocklistDocument>;
|
|
12
|
-
//# sourceMappingURL=token-blocklist.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-blocklist.d.ts","sourceRoot":"","sources":["../../src/models/token-blocklist.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,WAAW,sBAAuB,SAAQ,QAAQ;IACtD,GAAG,EAAE,MAAM,CAAC;CACb;AASD,eAAO,MAAM,cAAc;;;;;;+BAAiF,CAAC"}
|