@nexusrt/nexus-auth 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/README.md +277 -0
- package/dist/client.d.ts +106 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.mts +466 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +613 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +506 -0
- package/dist/methods/emailMfa.d.ts +18 -0
- package/dist/methods/emailMfa.d.ts.map +1 -0
- package/dist/methods/emailPassword.d.ts +52 -0
- package/dist/methods/emailPassword.d.ts.map +1 -0
- package/dist/methods/magicLink.d.ts +27 -0
- package/dist/methods/magicLink.d.ts.map +1 -0
- package/dist/methods/password.d.ts +37 -0
- package/dist/methods/password.d.ts.map +1 -0
- package/dist/methods/session.d.ts +27 -0
- package/dist/methods/session.d.ts.map +1 -0
- package/dist/methods/sso.d.ts +34 -0
- package/dist/methods/sso.d.ts.map +1 -0
- package/dist/methods/token.d.ts +32 -0
- package/dist/methods/token.d.ts.map +1 -0
- package/dist/methods/totp.d.ts +34 -0
- package/dist/methods/totp.d.ts.map +1 -0
- package/dist/providers/social.d.ts +23 -0
- package/dist/providers/social.d.ts.map +1 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SsoRegisterInput, SsoStartResponse, GenericSuccessResponse } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-tenant SSO (Single Sign-On) methods.
|
|
4
|
+
*
|
|
5
|
+
* Two roles exist:
|
|
6
|
+
*
|
|
7
|
+
* Organization Admin → registers their company IDP via `registerProvider()`.
|
|
8
|
+
* End User → signs in through their company's IDP via `signIn()`.
|
|
9
|
+
*
|
|
10
|
+
* Sign-in flow:
|
|
11
|
+
* 1. Call `signIn(email)` → server resolves the org and returns a redirect URL.
|
|
12
|
+
* 2. Redirect the user to `auth_url` to complete sign-in with their IDP.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const { auth_url } = await auth.sso.signIn(email);
|
|
16
|
+
* window.location.href = auth_url;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class SsoAuth {
|
|
20
|
+
/**
|
|
21
|
+
* Registers an organisation's Identity Provider (IDP) with the auth system.
|
|
22
|
+
* This is typically called once by an organisation admin during onboarding.
|
|
23
|
+
*/
|
|
24
|
+
registerProvider(baseUrl: string, input: SsoRegisterInput): Promise<GenericSuccessResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Starts the SSO sign-in flow for an end user.
|
|
27
|
+
*
|
|
28
|
+
* The server resolves the correct IDP from the user's email domain
|
|
29
|
+
* and returns a redirect URL. Redirect the user to that URL to
|
|
30
|
+
* complete sign-in with their organisation's IDP.
|
|
31
|
+
*/
|
|
32
|
+
signIn(baseUrl: string, email: string): Promise<SsoStartResponse>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=sso.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sso.d.ts","sourceRoot":"","sources":["../../src/methods/sso.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAO;IAClB;;;OAGG;IACG,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,sBAAsB,CAAC;IAiBlC;;;;;;OAMG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAMxE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AuthUser, JwtPayload } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Manages the in-memory access token and exposes token-based auth helpers.
|
|
4
|
+
*
|
|
5
|
+
* Storage strategy:
|
|
6
|
+
* - Access tokens are kept in memory only (never localStorage) for security.
|
|
7
|
+
* - Refresh tokens are stored in an HttpOnly cookie by the server.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TokenManager {
|
|
10
|
+
private accessToken;
|
|
11
|
+
getAccessToken(): string | null;
|
|
12
|
+
setAccessToken(token: string): void;
|
|
13
|
+
clearAccessToken(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Decodes the JWT payload without verifying the signature.
|
|
16
|
+
* Useful for reading user info (email, avatar) on the client side.
|
|
17
|
+
*/
|
|
18
|
+
decodeToken(token: string): JwtPayload | null;
|
|
19
|
+
/**
|
|
20
|
+
* Reads user info directly from the stored access token.
|
|
21
|
+
* Returns null if no token is set or it cannot be decoded.
|
|
22
|
+
*/
|
|
23
|
+
getUserFromToken(): Pick<AuthUser, "email" | "avatar_url"> | null;
|
|
24
|
+
/**
|
|
25
|
+
* Attempts to get a new access token using the HttpOnly refresh-token cookie.
|
|
26
|
+
* Call this once on app startup to restore an existing session.
|
|
27
|
+
*
|
|
28
|
+
* @returns The decoded user info on success, or null if no refresh token exists.
|
|
29
|
+
*/
|
|
30
|
+
refresh(baseUrl: string): Promise<Pick<AuthUser, "email" | "avatar_url"> | null>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/methods/token.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EAER,UAAU,EACX,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAuB;IAI1C,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC,gBAAgB,IAAI,IAAI;IAMxB;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAS7C;;;OAGG;IACH,gBAAgB,IAAI,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,YAAY,CAAC,GAAG,IAAI;IAYjE;;;;;OAKG;IACG,OAAO,CACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC;CAc1D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TotpSetupResponse, MfaVerifyInput, GenericSuccessResponse } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* TOTP (Time-based One-Time Password) MFA management.
|
|
4
|
+
*
|
|
5
|
+
* Setup flow:
|
|
6
|
+
* 1. Call `setup()` → server returns a QR code / TOTP secret.
|
|
7
|
+
* 2. User scans with their authenticator app.
|
|
8
|
+
* 3. Call `confirmSetup(code)` with the first code to activate TOTP.
|
|
9
|
+
*
|
|
10
|
+
* Sign-in flow (when signIn returns `status === SignInStatus.TOTP_REQUIRED`):
|
|
11
|
+
* 1. Prompt user for their TOTP code.
|
|
12
|
+
* 2. Call `verifySignIn({ sessionId: data.mfa, code })` to complete sign-in.
|
|
13
|
+
*/
|
|
14
|
+
export declare class TotpAuth {
|
|
15
|
+
/**
|
|
16
|
+
* Starts TOTP registration — returns the secret / QR code to display.
|
|
17
|
+
*/
|
|
18
|
+
setup(baseUrl: string): Promise<TotpSetupResponse>;
|
|
19
|
+
/**
|
|
20
|
+
* Confirms the TOTP setup by verifying the first code from the
|
|
21
|
+
* authenticator app.
|
|
22
|
+
*/
|
|
23
|
+
confirmSetup(baseUrl: string, code: string): Promise<GenericSuccessResponse>;
|
|
24
|
+
/**
|
|
25
|
+
* Completes a TOTP sign-in challenge.
|
|
26
|
+
* Use the `mfa` session ID returned by `emailPassword.signIn()` as `sessionId`.
|
|
27
|
+
*/
|
|
28
|
+
verifySignIn(baseUrl: string, input: MfaVerifyInput): Promise<GenericSuccessResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* Removes TOTP from the currently authenticated user's account.
|
|
31
|
+
*/
|
|
32
|
+
delete(baseUrl: string): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=totp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.d.ts","sourceRoot":"","sources":["../../src/methods/totp.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IACnB;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOxD;;;OAGG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,sBAAsB,CAAC;IAUlC;;;OAGG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,sBAAsB,CAAC;IAalC;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social / OAuth provider sign-in methods.
|
|
3
|
+
*
|
|
4
|
+
* Each method redirects the browser to the provider's OAuth flow.
|
|
5
|
+
* After successful authentication the server will redirect back to
|
|
6
|
+
* your application with a session cookie (or access token, depending
|
|
7
|
+
* on your server configuration).
|
|
8
|
+
*
|
|
9
|
+
* These are simple redirects — no async call is needed.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SocialAuth {
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
constructor(baseUrl: string);
|
|
14
|
+
/** Redirects to Google OAuth sign-in. */
|
|
15
|
+
signInWithGoogle(): void;
|
|
16
|
+
/** Redirects to GitHub OAuth sign-in. */
|
|
17
|
+
signInWithGithub(): void;
|
|
18
|
+
/** Redirects to LinkedIn OAuth sign-in. */
|
|
19
|
+
signInWithLinkedIn(): void;
|
|
20
|
+
/** Redirects to Okta OAuth sign-in. */
|
|
21
|
+
signInWithOkta(): void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=social.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/providers/social.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAE5C,yCAAyC;IACzC,gBAAgB,IAAI,IAAI;IAIxB,yCAAyC;IACzC,gBAAgB,IAAI,IAAI;IAIxB,2CAA2C;IAC3C,kBAAkB,IAAI,IAAI;IAI1B,uCAAuC;IACvC,cAAc,IAAI,IAAI;CAGvB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface AuthUser {
|
|
2
|
+
email: string;
|
|
3
|
+
avatar_url?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface JwtPayload {
|
|
7
|
+
email: string;
|
|
8
|
+
avatar_url?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface RefreshTokenResponse {
|
|
12
|
+
access_token: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SignInResponse {
|
|
15
|
+
access_token?: string;
|
|
16
|
+
status?: SignInStatus;
|
|
17
|
+
mfa?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MagicLinkResponse {
|
|
20
|
+
status: "MAGIC_LINK";
|
|
21
|
+
mfa: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ForgotPasswordResponse {
|
|
24
|
+
status: "RESET_PASSWORD";
|
|
25
|
+
mfa: string;
|
|
26
|
+
}
|
|
27
|
+
export interface TotpSetupResponse {
|
|
28
|
+
totp: string;
|
|
29
|
+
}
|
|
30
|
+
export interface SsoStartResponse {
|
|
31
|
+
auth_url: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SsoRegisterResponse {
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface GenericSuccessResponse {
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export declare const SignInStatus: {
|
|
40
|
+
/** Standard email+password sign-in succeeded — no MFA required */
|
|
41
|
+
readonly SUCCESS: "SUCCESS";
|
|
42
|
+
/** Server requires the user to complete an Email MFA challenge */
|
|
43
|
+
readonly MFA_REQUIRED: "MFA_REQUIRED";
|
|
44
|
+
/** Server requires the user to complete a TOTP challenge */
|
|
45
|
+
readonly TOTP_REQUIRED: "TOTP_REQUIRED";
|
|
46
|
+
/** Server sent a magic link/code to the user's email */
|
|
47
|
+
readonly MAGIC_LINK: "MAGIC_LINK";
|
|
48
|
+
/** Server sent a password-reset code to the user's email */
|
|
49
|
+
readonly RESET_PASSWORD: "RESET_PASSWORD";
|
|
50
|
+
};
|
|
51
|
+
export type SignInStatus = (typeof SignInStatus)[keyof typeof SignInStatus];
|
|
52
|
+
export interface AuthClientConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Base URL of your auth server.
|
|
55
|
+
* @default "https://auth.jobtrk.com"
|
|
56
|
+
*/
|
|
57
|
+
baseUrl?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface EmailPasswordCredentials {
|
|
60
|
+
email: string;
|
|
61
|
+
password: string;
|
|
62
|
+
userName?: string;
|
|
63
|
+
firstName?: string;
|
|
64
|
+
lastName?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface MfaVerifyInput {
|
|
67
|
+
/** The session ID returned from the initial sign-in / magic-link call */
|
|
68
|
+
sessionId: string;
|
|
69
|
+
/** The one-time code entered by the user */
|
|
70
|
+
code: string;
|
|
71
|
+
}
|
|
72
|
+
export interface ResetPasswordInput {
|
|
73
|
+
oldPassword: string;
|
|
74
|
+
newPassword: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ForgotPasswordConfirmInput {
|
|
77
|
+
/** The session ID returned from forgotPassword() */
|
|
78
|
+
sessionId: string;
|
|
79
|
+
/** The code emailed to the user */
|
|
80
|
+
code: string;
|
|
81
|
+
/** The new password to set */
|
|
82
|
+
password: string;
|
|
83
|
+
}
|
|
84
|
+
export interface SsoRegisterInput {
|
|
85
|
+
providerName: string;
|
|
86
|
+
providerEndEmail: string;
|
|
87
|
+
clientId: string;
|
|
88
|
+
clientSecret: string;
|
|
89
|
+
issuer: string;
|
|
90
|
+
callbackUrl: string;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAMD,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAOD,eAAO,MAAM,YAAY;IACvB,kEAAkE;;IAGlE,kEAAkE;;IAGlE,4DAA4D;;IAG5D,wDAAwD;;IAGxD,4DAA4D;;CAEpD,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAM5E,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexusrt/nexus-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Universal TypeScript SDK for the Nexus authentication system",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "rollup -c",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"auth",
|
|
27
|
+
"authentication",
|
|
28
|
+
"sdk",
|
|
29
|
+
"typescript",
|
|
30
|
+
"oauth",
|
|
31
|
+
"sso",
|
|
32
|
+
"mfa",
|
|
33
|
+
"totp"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
38
|
+
"rollup": "^4.59.0",
|
|
39
|
+
"tslib": "^2.8.1",
|
|
40
|
+
"tsup": "^8.0.0",
|
|
41
|
+
"typescript": "^5.4.0"
|
|
42
|
+
}
|
|
43
|
+
}
|