@versini/auth-common 2.10.1 → 2.11.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/dist/index.d.ts +15 -1
- package/dist/index.js +80 -62
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -59,4 +59,18 @@ declare function pkceChallengePair(length?: number): Promise<{
|
|
|
59
59
|
*/
|
|
60
60
|
declare function verifyChallenge(code_verifier: string, expectedChallenge: string): Promise<boolean>;
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
type HeadersLike = Record<string, unknown> & {
|
|
63
|
+
authorization?: string;
|
|
64
|
+
"content-type"?: string;
|
|
65
|
+
cookie?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Get a Bearer Token from a request.
|
|
69
|
+
*
|
|
70
|
+
* @param headers An object containing the request headers, usually `req.headers`.
|
|
71
|
+
* @param clientId The client ID to use.
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
declare const getToken: (headers: HeadersLike, clientId: string) => string;
|
|
75
|
+
|
|
76
|
+
export { API_TYPE, AUTH_TYPES, HEADERS, JWT, JWT_PUBLIC_KEY, TOKEN_EXPIRATION, decodeToken, generateCodeChallenge, getToken, pkceChallengePair, verifyAndExtractToken, verifyChallenge };
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/auth-common v2.
|
|
2
|
+
@versini/auth-common v2.11.0
|
|
3
3
|
© 2024 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
try {
|
|
6
6
|
window.__VERSINI_AUTH_COMMON__ || (window.__VERSINI_AUTH_COMMON__ = {
|
|
7
|
-
version: "2.
|
|
8
|
-
buildTime: "
|
|
7
|
+
version: "2.11.0",
|
|
8
|
+
buildTime: "07/07/2024 10:25 AM EDT",
|
|
9
9
|
homepage: "https://github.com/aversini/auth-client",
|
|
10
10
|
license: "MIT"
|
|
11
11
|
});
|
|
12
12
|
} catch {
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const Qe = {
|
|
15
15
|
ID_TOKEN: "id_token",
|
|
16
16
|
ACCESS_TOKEN: "token",
|
|
17
17
|
ID_AND_ACCESS_TOKEN: "id_token token",
|
|
18
18
|
CODE: "code",
|
|
19
19
|
REFRESH_TOKEN: "refresh_token"
|
|
20
|
-
},
|
|
20
|
+
}, Xe = {
|
|
21
21
|
CLIENT_ID: "X-Auth-ClientId"
|
|
22
22
|
}, N = {
|
|
23
23
|
ALG: "RS256",
|
|
@@ -34,15 +34,15 @@ aMwPFOIcJH+rKfFgNcHLcaS5syp7zU1ANwZ+trgR+DifBr8TLVkBynmNeTyhDm2+
|
|
|
34
34
|
l0haqjMk0UoNPPE8iYBWUHQJJE1Dqstj65d6Eh5g64Pao25y4cmYJbKjiblIGEkE
|
|
35
35
|
sjqybA9mARAqh9k/eiIopecWSiffNQTwVQVd2I9ZH3BalhEXHlqFgrjz51kFqg81
|
|
36
36
|
awIDAQAB
|
|
37
|
-
-----END PUBLIC KEY-----`,
|
|
37
|
+
-----END PUBLIC KEY-----`, Ze = {
|
|
38
38
|
ACCESS: "5m",
|
|
39
39
|
ID: "90d",
|
|
40
40
|
REFRESH: "90d"
|
|
41
|
-
},
|
|
41
|
+
}, je = {
|
|
42
42
|
AUTHENTICATE: "authenticate",
|
|
43
43
|
CODE: "code",
|
|
44
44
|
LOGOUT: "logout"
|
|
45
|
-
}, x = crypto,
|
|
45
|
+
}, x = crypto, z = (e) => e instanceof CryptoKey, v = new TextEncoder(), C = new TextDecoder();
|
|
46
46
|
function ae(...e) {
|
|
47
47
|
const t = e.reduce((a, { length: i }) => a + i, 0), r = new Uint8Array(t);
|
|
48
48
|
let n = 0;
|
|
@@ -73,7 +73,7 @@ class A extends Error {
|
|
|
73
73
|
super(t), this.code = "ERR_JOSE_GENERIC", this.name = this.constructor.name, (r = Error.captureStackTrace) == null || r.call(Error, this, this.constructor);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
class
|
|
76
|
+
class h extends A {
|
|
77
77
|
static get code() {
|
|
78
78
|
return "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
|
79
79
|
}
|
|
@@ -213,7 +213,7 @@ function ue(e, t, ...r) {
|
|
|
213
213
|
}
|
|
214
214
|
de(e, r);
|
|
215
215
|
}
|
|
216
|
-
function
|
|
216
|
+
function G(e, t, ...r) {
|
|
217
217
|
var n;
|
|
218
218
|
if (r.length > 2) {
|
|
219
219
|
const a = r.pop();
|
|
@@ -222,11 +222,11 @@ function Y(e, t, ...r) {
|
|
|
222
222
|
r.length === 2 ? e += `one of type ${r[0]} or ${r[1]}.` : e += `of type ${r[0]}.`;
|
|
223
223
|
return t == null ? e += ` Received ${t}` : typeof t == "function" && t.name ? e += ` Received function ${t.name}` : typeof t == "object" && t != null && (n = t.constructor) != null && n.name && (e += ` Received an instance of ${t.constructor.name}`), e;
|
|
224
224
|
}
|
|
225
|
-
const L = (e, ...t) =>
|
|
226
|
-
function
|
|
227
|
-
return
|
|
225
|
+
const L = (e, ...t) => G("Key must be ", e, ...t);
|
|
226
|
+
function Y(e, t, ...r) {
|
|
227
|
+
return G(`Key for the ${e} algorithm must be `, t, ...r);
|
|
228
228
|
}
|
|
229
|
-
const Q = (e) =>
|
|
229
|
+
const Q = (e) => z(e) ? !0 : (e == null ? void 0 : e[Symbol.toStringTag]) === "KeyObject", _ = ["CryptoKey"], le = (...e) => {
|
|
230
230
|
const t = e.filter(Boolean);
|
|
231
231
|
if (t.length === 0 || t.length === 1)
|
|
232
232
|
return !0;
|
|
@@ -245,11 +245,11 @@ const Q = (e) => G(e) ? !0 : (e == null ? void 0 : e[Symbol.toStringTag]) === "K
|
|
|
245
245
|
}
|
|
246
246
|
return !0;
|
|
247
247
|
};
|
|
248
|
-
function
|
|
248
|
+
function fe(e) {
|
|
249
249
|
return typeof e == "object" && e !== null;
|
|
250
250
|
}
|
|
251
251
|
function P(e) {
|
|
252
|
-
if (!
|
|
252
|
+
if (!fe(e) || Object.prototype.toString.call(e) !== "[object Object]")
|
|
253
253
|
return !1;
|
|
254
254
|
if (Object.getPrototypeOf(e) === null)
|
|
255
255
|
return !0;
|
|
@@ -258,7 +258,7 @@ function P(e) {
|
|
|
258
258
|
t = Object.getPrototypeOf(t);
|
|
259
259
|
return Object.getPrototypeOf(e) === t;
|
|
260
260
|
}
|
|
261
|
-
const
|
|
261
|
+
const he = (e, t) => {
|
|
262
262
|
if (e.startsWith("RS") || e.startsWith("PS")) {
|
|
263
263
|
const { modulusLength: r } = t.algorithm;
|
|
264
264
|
if (typeof r != "number" || r < 2048)
|
|
@@ -448,13 +448,13 @@ async function Ae(e, t, r) {
|
|
|
448
448
|
const R = (e) => e == null ? void 0 : e[Symbol.toStringTag], be = (e, t) => {
|
|
449
449
|
if (!(t instanceof Uint8Array)) {
|
|
450
450
|
if (!Q(t))
|
|
451
|
-
throw new TypeError(
|
|
451
|
+
throw new TypeError(Y(e, t, ..._, "Uint8Array"));
|
|
452
452
|
if (t.type !== "secret")
|
|
453
453
|
throw new TypeError(`${R(t)} instances for symmetric algorithms must be of type "secret"`);
|
|
454
454
|
}
|
|
455
455
|
}, Ce = (e, t, r) => {
|
|
456
456
|
if (!Q(t))
|
|
457
|
-
throw new TypeError(
|
|
457
|
+
throw new TypeError(Y(e, t, ..._));
|
|
458
458
|
if (t.type === "secret")
|
|
459
459
|
throw new TypeError(`${R(t)} instances for asymmetric algorithms must not be of type "secret"`);
|
|
460
460
|
if (t.algorithm && r === "verify" && t.type === "private")
|
|
@@ -515,7 +515,7 @@ function Re(e, t) {
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
async function _e(e, t, r) {
|
|
518
|
-
if (t = await Ee.normalizePublicKey(t, e),
|
|
518
|
+
if (t = await Ee.normalizePublicKey(t, e), z(t))
|
|
519
519
|
return ue(t, e, r), t;
|
|
520
520
|
if (t instanceof Uint8Array) {
|
|
521
521
|
if (!e.startsWith("HS"))
|
|
@@ -526,7 +526,7 @@ async function _e(e, t, r) {
|
|
|
526
526
|
}
|
|
527
527
|
const Pe = async (e, t, r, n) => {
|
|
528
528
|
const a = await _e(e, t, "verify");
|
|
529
|
-
|
|
529
|
+
he(e, a);
|
|
530
530
|
const i = Re(e, a.algorithm);
|
|
531
531
|
try {
|
|
532
532
|
return await x.subtle.verify(i, a, r, n);
|
|
@@ -578,13 +578,13 @@ async function xe(e, t, r) {
|
|
|
578
578
|
let p = !1;
|
|
579
579
|
typeof t == "function" && (t = await t(n, e), p = !0), ve(c, t, "verify");
|
|
580
580
|
const g = ae(v.encode(e.protected ?? ""), v.encode("."), typeof e.payload == "string" ? v.encode(e.payload) : e.payload);
|
|
581
|
-
let
|
|
581
|
+
let f;
|
|
582
582
|
try {
|
|
583
|
-
|
|
583
|
+
f = b(e.signature);
|
|
584
584
|
} catch {
|
|
585
585
|
throw new u("Failed to base64url decode the signature");
|
|
586
586
|
}
|
|
587
|
-
if (!await Pe(c, t,
|
|
587
|
+
if (!await Pe(c, t, f, g))
|
|
588
588
|
throw new ce();
|
|
589
589
|
let y;
|
|
590
590
|
if (o)
|
|
@@ -607,7 +607,7 @@ async function Ke(e, t, r) {
|
|
|
607
607
|
const c = await xe({ payload: a, protected: n, signature: i }, t, r), s = { payload: c.payload, protectedHeader: c.protectedHeader };
|
|
608
608
|
return typeof t == "function" ? { ...s, key: c.key } : s;
|
|
609
609
|
}
|
|
610
|
-
const Oe = (e) => Math.floor(e.getTime() / 1e3), ee = 60, te = ee * 60, H = te * 24, We = H * 7, Je = H * 365.25, De = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i,
|
|
610
|
+
const Oe = (e) => Math.floor(e.getTime() / 1e3), ee = 60, te = ee * 60, H = te * 24, We = H * 7, Je = H * 365.25, De = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i, k = (e) => {
|
|
611
611
|
const t = De.exec(e);
|
|
612
612
|
if (!t || t[4] && t[1])
|
|
613
613
|
throw new TypeError("Invalid time period format");
|
|
@@ -650,7 +650,7 @@ const Oe = (e) => Math.floor(e.getTime() / 1e3), ee = 60, te = ee * 60, H = te *
|
|
|
650
650
|
break;
|
|
651
651
|
}
|
|
652
652
|
return t[1] === "-" || t[4] === "ago" ? -a : a;
|
|
653
|
-
},
|
|
653
|
+
}, V = (e) => e.toLowerCase().replace(/^application\//, ""), He = (e, t) => typeof e == "string" ? t.includes(e) : Array.isArray(e) ? t.some(Set.prototype.has.bind(new Set(e))) : !1, Ue = (e, t, r = {}) => {
|
|
654
654
|
let n;
|
|
655
655
|
try {
|
|
656
656
|
n = JSON.parse(C.decode(t));
|
|
@@ -659,54 +659,54 @@ const Oe = (e) => Math.floor(e.getTime() / 1e3), ee = 60, te = ee * 60, H = te *
|
|
|
659
659
|
if (!P(n))
|
|
660
660
|
throw new S("JWT Claims Set must be a top-level JSON object");
|
|
661
661
|
const { typ: a } = r;
|
|
662
|
-
if (a && (typeof e.typ != "string" ||
|
|
663
|
-
throw new
|
|
662
|
+
if (a && (typeof e.typ != "string" || V(e.typ) !== V(a)))
|
|
663
|
+
throw new h('unexpected "typ" JWT header value', n, "typ", "check_failed");
|
|
664
664
|
const { requiredClaims: i = [], issuer: o, subject: c, audience: s, maxTokenAge: p } = r, g = [...i];
|
|
665
665
|
p !== void 0 && g.push("iat"), s !== void 0 && g.push("aud"), c !== void 0 && g.push("sub"), o !== void 0 && g.push("iss");
|
|
666
666
|
for (const l of new Set(g.reverse()))
|
|
667
667
|
if (!(l in n))
|
|
668
|
-
throw new
|
|
668
|
+
throw new h(`missing required "${l}" claim`, n, l, "missing");
|
|
669
669
|
if (o && !(Array.isArray(o) ? o : [o]).includes(n.iss))
|
|
670
|
-
throw new
|
|
670
|
+
throw new h('unexpected "iss" claim value', n, "iss", "check_failed");
|
|
671
671
|
if (c && n.sub !== c)
|
|
672
|
-
throw new
|
|
672
|
+
throw new h('unexpected "sub" claim value', n, "sub", "check_failed");
|
|
673
673
|
if (s && !He(n.aud, typeof s == "string" ? [s] : s))
|
|
674
|
-
throw new
|
|
675
|
-
let
|
|
674
|
+
throw new h('unexpected "aud" claim value', n, "aud", "check_failed");
|
|
675
|
+
let f;
|
|
676
676
|
switch (typeof r.clockTolerance) {
|
|
677
677
|
case "string":
|
|
678
|
-
|
|
678
|
+
f = k(r.clockTolerance);
|
|
679
679
|
break;
|
|
680
680
|
case "number":
|
|
681
|
-
|
|
681
|
+
f = r.clockTolerance;
|
|
682
682
|
break;
|
|
683
683
|
case "undefined":
|
|
684
|
-
|
|
684
|
+
f = 0;
|
|
685
685
|
break;
|
|
686
686
|
default:
|
|
687
687
|
throw new TypeError("Invalid clockTolerance option type");
|
|
688
688
|
}
|
|
689
689
|
const { currentDate: U } = r, y = Oe(U || /* @__PURE__ */ new Date());
|
|
690
690
|
if ((n.iat !== void 0 || p) && typeof n.iat != "number")
|
|
691
|
-
throw new
|
|
691
|
+
throw new h('"iat" claim must be a number', n, "iat", "invalid");
|
|
692
692
|
if (n.nbf !== void 0) {
|
|
693
693
|
if (typeof n.nbf != "number")
|
|
694
|
-
throw new
|
|
695
|
-
if (n.nbf > y +
|
|
696
|
-
throw new
|
|
694
|
+
throw new h('"nbf" claim must be a number', n, "nbf", "invalid");
|
|
695
|
+
if (n.nbf > y + f)
|
|
696
|
+
throw new h('"nbf" claim timestamp check failed', n, "nbf", "check_failed");
|
|
697
697
|
}
|
|
698
698
|
if (n.exp !== void 0) {
|
|
699
699
|
if (typeof n.exp != "number")
|
|
700
|
-
throw new
|
|
701
|
-
if (n.exp <= y -
|
|
700
|
+
throw new h('"exp" claim must be a number', n, "exp", "invalid");
|
|
701
|
+
if (n.exp <= y - f)
|
|
702
702
|
throw new $('"exp" claim timestamp check failed', n, "exp", "check_failed");
|
|
703
703
|
}
|
|
704
704
|
if (p) {
|
|
705
|
-
const l = y - n.iat, K = typeof p == "number" ? p :
|
|
706
|
-
if (l -
|
|
705
|
+
const l = y - n.iat, K = typeof p == "number" ? p : k(p);
|
|
706
|
+
if (l - f > K)
|
|
707
707
|
throw new $('"iat" claim timestamp check failed (too far in the past)', n, "iat", "check_failed");
|
|
708
|
-
if (l < 0 -
|
|
709
|
-
throw new
|
|
708
|
+
if (l < 0 - f)
|
|
709
|
+
throw new h('"iat" claim timestamp check failed (it should be in the past)', n, "iat", "check_failed");
|
|
710
710
|
}
|
|
711
711
|
return n;
|
|
712
712
|
};
|
|
@@ -745,7 +745,7 @@ function Le(e) {
|
|
|
745
745
|
throw new S("Invalid JWT Claims Set");
|
|
746
746
|
return a;
|
|
747
747
|
}
|
|
748
|
-
const
|
|
748
|
+
const et = async (e) => {
|
|
749
749
|
try {
|
|
750
750
|
const t = N.ALG, n = await Ae(ne, t);
|
|
751
751
|
return await Ne(e, n, {
|
|
@@ -754,7 +754,7 @@ const Xe = async (e) => {
|
|
|
754
754
|
} catch {
|
|
755
755
|
return;
|
|
756
756
|
}
|
|
757
|
-
},
|
|
757
|
+
}, tt = (e) => {
|
|
758
758
|
try {
|
|
759
759
|
return Le(e);
|
|
760
760
|
} catch {
|
|
@@ -767,11 +767,11 @@ for (var D = 0; D < 256; ++D)
|
|
|
767
767
|
function Me(e, t = 0) {
|
|
768
768
|
return (d[e[t + 0]] + d[e[t + 1]] + d[e[t + 2]] + d[e[t + 3]] + "-" + d[e[t + 4]] + d[e[t + 5]] + "-" + d[e[t + 6]] + d[e[t + 7]] + "-" + d[e[t + 8]] + d[e[t + 9]] + "-" + d[e[t + 10]] + d[e[t + 11]] + d[e[t + 12]] + d[e[t + 13]] + d[e[t + 14]] + d[e[t + 15]]).toLowerCase();
|
|
769
769
|
}
|
|
770
|
-
var I,
|
|
771
|
-
function
|
|
770
|
+
var I, ke = new Uint8Array(16);
|
|
771
|
+
function Ve() {
|
|
772
772
|
if (!I && (I = typeof crypto < "u" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto), !I))
|
|
773
773
|
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
|
774
|
-
return I(
|
|
774
|
+
return I(ke);
|
|
775
775
|
}
|
|
776
776
|
var Be = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
777
777
|
const B = {
|
|
@@ -781,7 +781,7 @@ function F(e, t, r) {
|
|
|
781
781
|
if (B.randomUUID && !t && !e)
|
|
782
782
|
return B.randomUUID();
|
|
783
783
|
e = e || {};
|
|
784
|
-
var n = e.random || (e.rng ||
|
|
784
|
+
var n = e.random || (e.rng || Ve)();
|
|
785
785
|
return n[6] = n[6] & 15 | 64, n[8] = n[8] & 63 | 128, Me(n);
|
|
786
786
|
}
|
|
787
787
|
const q = globalThis.crypto, Fe = (e) => `${F()}${F()}`.slice(0, e), qe = (e) => btoa(
|
|
@@ -795,7 +795,7 @@ async function re(e) {
|
|
|
795
795
|
const t = new TextEncoder().encode(e), r = await q.subtle.digest("SHA-256", t);
|
|
796
796
|
return qe(r).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
797
797
|
}
|
|
798
|
-
async function
|
|
798
|
+
async function rt(e) {
|
|
799
799
|
const t = e || 43;
|
|
800
800
|
if (t < 43 || t > 128)
|
|
801
801
|
throw `Expected a length between 43 and 128. Received ${e}.`;
|
|
@@ -805,19 +805,37 @@ async function je(e) {
|
|
|
805
805
|
code_challenge: n
|
|
806
806
|
};
|
|
807
807
|
}
|
|
808
|
-
async function
|
|
808
|
+
async function nt(e, t) {
|
|
809
809
|
return t === await re(e);
|
|
810
810
|
}
|
|
811
|
+
const ze = /^Bearer (.+)$/i, Ge = (e) => {
|
|
812
|
+
if (typeof e.authorization != "string")
|
|
813
|
+
return;
|
|
814
|
+
const t = e.authorization.match(ze);
|
|
815
|
+
if (t)
|
|
816
|
+
return t[1];
|
|
817
|
+
}, Ye = (e, t) => {
|
|
818
|
+
const r = e.cookie, n = new RegExp(`auth.${t}=(.+?)(?:;|$)`);
|
|
819
|
+
if (typeof r != "string")
|
|
820
|
+
return;
|
|
821
|
+
const a = r.match(n);
|
|
822
|
+
if (a)
|
|
823
|
+
return a[1];
|
|
824
|
+
}, at = (e, t) => {
|
|
825
|
+
const r = Ge(e), n = Ye(e, t);
|
|
826
|
+
return !n && !r ? "" : n || r;
|
|
827
|
+
};
|
|
811
828
|
export {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
829
|
+
je as API_TYPE,
|
|
830
|
+
Qe as AUTH_TYPES,
|
|
831
|
+
Xe as HEADERS,
|
|
815
832
|
N as JWT,
|
|
816
833
|
ne as JWT_PUBLIC_KEY,
|
|
817
|
-
|
|
818
|
-
|
|
834
|
+
Ze as TOKEN_EXPIRATION,
|
|
835
|
+
tt as decodeToken,
|
|
819
836
|
re as generateCodeChallenge,
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
et as
|
|
837
|
+
at as getToken,
|
|
838
|
+
rt as pkceChallengePair,
|
|
839
|
+
et as verifyAndExtractToken,
|
|
840
|
+
nt as verifyChallenge
|
|
823
841
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/auth-common",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Arno Versini",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"test": "vitest run"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"jose": "5.6.
|
|
35
|
+
"jose": "5.6.3",
|
|
36
36
|
"uuid": "10.0.0"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "e6e92b161c834eba2f655e9e6b80bdd7b5cb2316"
|
|
39
39
|
}
|