@happyvertical/smrt-users 0.30.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/AGENTS.md +85 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +459 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/chunks/TerminalAuthService-DoAMQ_yn.js +5118 -0
- package/dist/chunks/TerminalAuthService-DoAMQ_yn.js.map +1 -0
- package/dist/chunks/index-DkoYIvIu.js +169 -0
- package/dist/chunks/index-DkoYIvIu.js.map +1 -0
- package/dist/collections/CliAuthRequestCollection.d.ts +19 -0
- package/dist/collections/CliAuthRequestCollection.d.ts.map +1 -0
- package/dist/collections/GroupCollection.d.ts +17 -0
- package/dist/collections/GroupCollection.d.ts.map +1 -0
- package/dist/collections/GroupMemberCollection.d.ts +43 -0
- package/dist/collections/GroupMemberCollection.d.ts.map +1 -0
- package/dist/collections/GroupRoleCollection.d.ts +33 -0
- package/dist/collections/GroupRoleCollection.d.ts.map +1 -0
- package/dist/collections/MagicLinkTokenCollection.d.ts +26 -0
- package/dist/collections/MagicLinkTokenCollection.d.ts.map +1 -0
- package/dist/collections/MembershipCollection.d.ts +38 -0
- package/dist/collections/MembershipCollection.d.ts.map +1 -0
- package/dist/collections/MembershipOverrideCollection.d.ts +55 -0
- package/dist/collections/MembershipOverrideCollection.d.ts.map +1 -0
- package/dist/collections/PermissionCollection.d.ts +34 -0
- package/dist/collections/PermissionCollection.d.ts.map +1 -0
- package/dist/collections/RoleCollection.d.ts +29 -0
- package/dist/collections/RoleCollection.d.ts.map +1 -0
- package/dist/collections/RolePermissionCollection.d.ts +33 -0
- package/dist/collections/RolePermissionCollection.d.ts.map +1 -0
- package/dist/collections/SessionCollection.d.ts +82 -0
- package/dist/collections/SessionCollection.d.ts.map +1 -0
- package/dist/collections/TenantCollection.d.ts +119 -0
- package/dist/collections/TenantCollection.d.ts.map +1 -0
- package/dist/collections/TenantPermissionOverrideCollection.d.ts +111 -0
- package/dist/collections/TenantPermissionOverrideCollection.d.ts.map +1 -0
- package/dist/collections/UserCollection.d.ts +116 -0
- package/dist/collections/UserCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +19 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1482 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +5216 -0
- package/dist/models/CliAuthRequest.d.ts +25 -0
- package/dist/models/CliAuthRequest.d.ts.map +1 -0
- package/dist/models/Group.d.ts +34 -0
- package/dist/models/Group.d.ts.map +1 -0
- package/dist/models/GroupMember.d.ts +29 -0
- package/dist/models/GroupMember.d.ts.map +1 -0
- package/dist/models/GroupRole.d.ts +29 -0
- package/dist/models/GroupRole.d.ts.map +1 -0
- package/dist/models/MagicLinkToken.d.ts +22 -0
- package/dist/models/MagicLinkToken.d.ts.map +1 -0
- package/dist/models/Membership.d.ts +48 -0
- package/dist/models/Membership.d.ts.map +1 -0
- package/dist/models/MembershipOverride.d.ts +50 -0
- package/dist/models/MembershipOverride.d.ts.map +1 -0
- package/dist/models/Permission.d.ts +79 -0
- package/dist/models/Permission.d.ts.map +1 -0
- package/dist/models/Role.d.ts +67 -0
- package/dist/models/Role.d.ts.map +1 -0
- package/dist/models/RolePermission.d.ts +29 -0
- package/dist/models/RolePermission.d.ts.map +1 -0
- package/dist/models/Session.d.ts +105 -0
- package/dist/models/Session.d.ts.map +1 -0
- package/dist/models/Tenant.d.ts +138 -0
- package/dist/models/Tenant.d.ts.map +1 -0
- package/dist/models/TenantPermissionOverride.d.ts +74 -0
- package/dist/models/TenantPermissionOverride.d.ts.map +1 -0
- package/dist/models/User.d.ts +72 -0
- package/dist/models/User.d.ts.map +1 -0
- package/dist/models/index.d.ts +19 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +139 -0
- package/dist/playground.js.map +1 -0
- package/dist/services/MagicLinkService.d.ts +84 -0
- package/dist/services/MagicLinkService.d.ts.map +1 -0
- package/dist/services/OidcLoginService.d.ts +134 -0
- package/dist/services/OidcLoginService.d.ts.map +1 -0
- package/dist/services/PermissionCatalogService.d.ts +62 -0
- package/dist/services/PermissionCatalogService.d.ts.map +1 -0
- package/dist/services/PermissionResolver.d.ts +150 -0
- package/dist/services/PermissionResolver.d.ts.map +1 -0
- package/dist/services/PostgresPermissionPolicies.d.ts +29 -0
- package/dist/services/PostgresPermissionPolicies.d.ts.map +1 -0
- package/dist/services/SessionPermissionContext.d.ts +43 -0
- package/dist/services/SessionPermissionContext.d.ts.map +1 -0
- package/dist/services/SessionService.d.ts +139 -0
- package/dist/services/SessionService.d.ts.map +1 -0
- package/dist/services/TenantService.d.ts +135 -0
- package/dist/services/TenantService.d.ts.map +1 -0
- package/dist/services/TerminalAuthService.d.ts +189 -0
- package/dist/services/TerminalAuthService.d.ts.map +1 -0
- package/dist/services/index.d.ts +14 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +2744 -0
- package/dist/svelte/components/InviteUserModal.svelte +351 -0
- package/dist/svelte/components/InviteUserModal.svelte.d.ts +17 -0
- package/dist/svelte/components/InviteUserModal.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserAvatar.svelte +105 -0
- package/dist/svelte/components/UserAvatar.svelte.d.ts +10 -0
- package/dist/svelte/components/UserAvatar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserCard.svelte +179 -0
- package/dist/svelte/components/UserCard.svelte.d.ts +18 -0
- package/dist/svelte/components/UserCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserForm.svelte +194 -0
- package/dist/svelte/components/UserForm.svelte.d.ts +18 -0
- package/dist/svelte/components/UserForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserList.svelte +107 -0
- package/dist/svelte/components/UserList.svelte.d.ts +20 -0
- package/dist/svelte/components/UserList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserMenu.svelte +326 -0
- package/dist/svelte/components/UserMenu.svelte.d.ts +33 -0
- package/dist/svelte/components/UserMenu.svelte.d.ts.map +1 -0
- package/dist/svelte/components/__tests__/InviteUserModal.test.js +54 -0
- package/dist/svelte/components/__tests__/UserAvatar.test.js +31 -0
- package/dist/svelte/components/__tests__/UserCard.test.js +39 -0
- package/dist/svelte/components/__tests__/UserForm.test.js +50 -0
- package/dist/svelte/components/__tests__/UserList.test.js +48 -0
- package/dist/svelte/components/__tests__/UserMenu.test.js +38 -0
- package/dist/svelte/i18n.d.ts +15 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +15 -0
- package/dist/svelte/index.d.ts +23 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +27 -0
- package/dist/svelte/playground.d.ts +151 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +134 -0
- package/dist/sveltekit/index.d.ts +379 -0
- package/dist/sveltekit/index.d.ts.map +1 -0
- package/dist/sveltekit/resource-list-handler.d.ts +127 -0
- package/dist/sveltekit/resource-list-handler.d.ts.map +1 -0
- package/dist/sveltekit/types.d.ts +31 -0
- package/dist/sveltekit/types.d.ts.map +1 -0
- package/dist/sveltekit.d.ts +2 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +978 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +75 -0
- package/dist/ui.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* CLI auth request lifecycle states.
|
|
4
|
+
*/
|
|
5
|
+
export type CliAuthRequestStatus = 'pending' | 'approved' | 'expired';
|
|
6
|
+
export declare class UsersCliAuthRequest extends SmrtObject {
|
|
7
|
+
/** Short, human-typeable code the user enters in the browser to approve the session. */
|
|
8
|
+
userCode: string;
|
|
9
|
+
/** SHA-256 hash of the long device code the CLI keeps secret. */
|
|
10
|
+
deviceCodeHash: string;
|
|
11
|
+
/** Lifecycle state — `pending` → `approved` | `expired`. */
|
|
12
|
+
status: CliAuthRequestStatus;
|
|
13
|
+
/** User id of the human who approved the request (set on approval). */
|
|
14
|
+
userId: string;
|
|
15
|
+
/** Tenant id captured from the approving session (set on approval). */
|
|
16
|
+
tenantId: string;
|
|
17
|
+
/** Session id minted on approval — handed to the CLI as its bearer token. */
|
|
18
|
+
sessionId: string;
|
|
19
|
+
/** When the pending request stops accepting approvals. */
|
|
20
|
+
expiresAt: Date;
|
|
21
|
+
/** When approval happened — null while the request is still pending. */
|
|
22
|
+
approvedAt: Date | null;
|
|
23
|
+
}
|
|
24
|
+
export { UsersCliAuthRequest as CliAuthRequest };
|
|
25
|
+
//# sourceMappingURL=CliAuthRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CliAuthRequest.d.ts","sourceRoot":"","sources":["../../src/models/CliAuthRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAS,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEtE,qBAMa,mBAAoB,SAAQ,UAAU;IACjD,wFAAwF;IAExF,QAAQ,SAAM;IAEd,iEAAiE;IAEjE,cAAc,SAAM;IAEpB,4DAA4D;IAE5D,MAAM,EAAE,oBAAoB,CAAa;IAEzC,uEAAuE;IAEvE,MAAM,SAAM;IAEZ,uEAAuE;IAEvE,QAAQ,SAAM;IAEd,6EAA6E;IAE7E,SAAS,SAAM;IAEf,0DAA0D;IAE1D,SAAS,OAAc;IAEvB,wEAAwE;IAExE,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;CAChC;AAED,OAAO,EAAE,mBAAmB,IAAI,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Group represents a team or department within a tenant.
|
|
4
|
+
*
|
|
5
|
+
* Groups can have roles assigned to them via GroupRole.
|
|
6
|
+
* Users in a group inherit permissions from the group's roles.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const editorsGroup = await groups.create({
|
|
11
|
+
* tenantId: tenant.id,
|
|
12
|
+
* name: 'Editorial Team',
|
|
13
|
+
* slug: 'editorial-team',
|
|
14
|
+
* description: 'Content editors and writers'
|
|
15
|
+
* });
|
|
16
|
+
* await editorsGroup.save();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class Group extends SmrtObject {
|
|
20
|
+
/**
|
|
21
|
+
* Foreign key to Tenant
|
|
22
|
+
*/
|
|
23
|
+
tenantId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Display name for the group
|
|
26
|
+
*/
|
|
27
|
+
name: string;
|
|
28
|
+
/**
|
|
29
|
+
* Description of the group
|
|
30
|
+
*/
|
|
31
|
+
description: string;
|
|
32
|
+
constructor(options?: any);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=Group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Group.d.ts","sourceRoot":"","sources":["../../src/models/Group.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAOa,KAAM,SAAQ,UAAU;IACnC;;OAEG;IAEH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;gBAEb,OAAO,GAAE,GAAQ;CAO9B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* GroupMember is a join table linking Users to Groups.
|
|
4
|
+
*
|
|
5
|
+
* A user can belong to multiple groups within a tenant.
|
|
6
|
+
* Group membership grants additional permissions via GroupRole.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Add user to a group
|
|
11
|
+
* const groupMember = await groupMembers.create({
|
|
12
|
+
* groupId: editorsGroup.id,
|
|
13
|
+
* userId: user.id
|
|
14
|
+
* });
|
|
15
|
+
* await groupMember.save();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class GroupMember extends SmrtObject {
|
|
19
|
+
/**
|
|
20
|
+
* Foreign key to Group
|
|
21
|
+
*/
|
|
22
|
+
groupId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Foreign key to User
|
|
25
|
+
*/
|
|
26
|
+
userId?: string;
|
|
27
|
+
constructor(options?: any);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=GroupMember.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupMember.d.ts","sourceRoot":"","sources":["../../src/models/GroupMember.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE;;;;;;;;;;;;;;;GAeG;AACH,qBAQa,WAAY,SAAQ,UAAU;IACzC;;OAEG;IAEH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IAEH,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,OAAO,GAAE,GAAQ;CAK9B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* GroupRole is a join table linking Groups to Roles.
|
|
4
|
+
*
|
|
5
|
+
* Groups can have multiple roles assigned, and members of the group
|
|
6
|
+
* inherit the permissions from all assigned roles.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Assign a role to a group
|
|
11
|
+
* const groupRole = await groupRoles.create({
|
|
12
|
+
* groupId: editorsGroup.id,
|
|
13
|
+
* roleId: editorRole.id
|
|
14
|
+
* });
|
|
15
|
+
* await groupRole.save();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class GroupRole extends SmrtObject {
|
|
19
|
+
/**
|
|
20
|
+
* Foreign key to Group
|
|
21
|
+
*/
|
|
22
|
+
groupId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Foreign key to Role
|
|
25
|
+
*/
|
|
26
|
+
roleId?: string;
|
|
27
|
+
constructor(options?: any);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=GroupRole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupRole.d.ts","sourceRoot":"","sources":["../../src/models/GroupRole.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE;;;;;;;;;;;;;;;GAeG;AACH,qBAQa,SAAU,SAAQ,UAAU;IACvC;;OAEG;IAEH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IAEH,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,OAAO,GAAE,GAAQ;CAK9B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Default magic link token expiry: 10 minutes in seconds
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_TOKEN_EXPIRY_SECONDS: number;
|
|
6
|
+
export declare class UsersMagicLinkToken extends SmrtObject {
|
|
7
|
+
/** Unique nonce embedded in the signed JWT */
|
|
8
|
+
nonce: string;
|
|
9
|
+
/** Email address this token was generated for */
|
|
10
|
+
email: string;
|
|
11
|
+
/** Whether this token has been used */
|
|
12
|
+
used: boolean;
|
|
13
|
+
/** When this token expires */
|
|
14
|
+
expiresAt: Date;
|
|
15
|
+
constructor(options?: any);
|
|
16
|
+
/** Check if the token has expired */
|
|
17
|
+
isExpired(): boolean;
|
|
18
|
+
/** Check if the token is still valid (unused and not expired) */
|
|
19
|
+
isValid(): boolean;
|
|
20
|
+
}
|
|
21
|
+
export { UsersMagicLinkToken as MagicLinkToken };
|
|
22
|
+
//# sourceMappingURL=MagicLinkToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MagicLinkToken.d.ts","sourceRoot":"","sources":["../../src/models/MagicLinkToken.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAS,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAEnE;;GAEG;AACH,eAAO,MAAM,4BAA4B,QAAU,CAAC;AAEpD,qBAOa,mBAAoB,SAAQ,UAAU;IACjD,8CAA8C;IAE9C,KAAK,EAAE,MAAM,CAAM;IAEnB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAM;IAEnB,uCAAuC;IACvC,IAAI,EAAE,OAAO,CAAS;IAEtB,8BAA8B;IAC9B,SAAS,EAAE,IAAI,CAA8D;gBAEjE,OAAO,GAAE,GAAQ;IAa7B,qCAAqC;IACrC,SAAS,IAAI,OAAO;IAIpB,iEAAiE;IACjE,OAAO,IAAI,OAAO;CAGnB;AAED,OAAO,EAAE,mBAAmB,IAAI,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { Membership as MembershipContract } from '@happyvertical/smrt-types';
|
|
3
|
+
import { MembershipStatus } from '../types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Membership represents a user's membership in a tenant with an assigned role.
|
|
6
|
+
*
|
|
7
|
+
* A user can have only one membership per tenant (UNIQUE userId + tenantId).
|
|
8
|
+
* The role determines the base permissions for the user in that tenant.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const membership = await memberships.create({
|
|
13
|
+
* userId: user.id,
|
|
14
|
+
* tenantId: tenant.id,
|
|
15
|
+
* roleId: role.id,
|
|
16
|
+
* status: MembershipStatus.ACTIVE
|
|
17
|
+
* });
|
|
18
|
+
* await membership.save();
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class Membership extends SmrtObject implements MembershipContract {
|
|
22
|
+
/**
|
|
23
|
+
* Foreign key to User
|
|
24
|
+
*/
|
|
25
|
+
userId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Foreign key to Tenant
|
|
28
|
+
*/
|
|
29
|
+
tenantId?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Foreign key to Role - determines base permissions
|
|
32
|
+
*/
|
|
33
|
+
roleId?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Membership status
|
|
36
|
+
*/
|
|
37
|
+
status: MembershipStatus;
|
|
38
|
+
constructor(options?: any);
|
|
39
|
+
/**
|
|
40
|
+
* Check if membership is active
|
|
41
|
+
*/
|
|
42
|
+
isActive(): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Check if membership is pending (invitation not yet accepted)
|
|
45
|
+
*/
|
|
46
|
+
isPending(): boolean;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=Membership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Membership.d.ts","sourceRoot":"","sources":["../../src/models/Membership.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,IAAI,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAQa,UAAW,SAAQ,UAAW,YAAW,kBAAkB;IACtE;;OAEG;IAEH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IAEH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IAEH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IAEH,MAAM,EAAE,gBAAgB,CAA2B;gBAEvC,OAAO,GAAE,GAAQ;IAQ7B;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,SAAS,IAAI,OAAO;CAGrB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { OverrideEffect } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* MembershipOverride allows granting or denying specific permissions
|
|
5
|
+
* to a user beyond what their role and groups provide.
|
|
6
|
+
*
|
|
7
|
+
* Overrides are applied after role and group permissions are resolved.
|
|
8
|
+
* DENY overrides take precedence over GRANT overrides.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Grant a specific permission to a user
|
|
13
|
+
* const override = await membershipOverrides.create({
|
|
14
|
+
* membershipId: membership.id,
|
|
15
|
+
* permissionId: specialPermission.id,
|
|
16
|
+
* effect: OverrideEffect.GRANT
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Deny a permission (even if role grants it)
|
|
20
|
+
* const deny = await membershipOverrides.create({
|
|
21
|
+
* membershipId: membership.id,
|
|
22
|
+
* permissionId: dangerousPermission.id,
|
|
23
|
+
* effect: OverrideEffect.DENY
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class MembershipOverride extends SmrtObject {
|
|
28
|
+
/**
|
|
29
|
+
* Foreign key to Membership
|
|
30
|
+
*/
|
|
31
|
+
membershipId?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Foreign key to Permission
|
|
34
|
+
*/
|
|
35
|
+
permissionId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Effect of the override: grant or deny
|
|
38
|
+
*/
|
|
39
|
+
effect: OverrideEffect;
|
|
40
|
+
constructor(options?: any);
|
|
41
|
+
/**
|
|
42
|
+
* Check if this override grants the permission
|
|
43
|
+
*/
|
|
44
|
+
isGrant(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Check if this override denies the permission
|
|
47
|
+
*/
|
|
48
|
+
isDeny(): boolean;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=MembershipOverride.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MembershipOverride.d.ts","sourceRoot":"","sources":["../../src/models/MembershipOverride.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAQa,kBAAmB,SAAQ,UAAU;IAChD;;OAEG;IAEH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IAEH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IAEH,MAAM,EAAE,cAAc,CAAwB;gBAElC,OAAO,GAAE,GAAQ;IAS7B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Parsed permission slug components
|
|
4
|
+
*/
|
|
5
|
+
export interface ParsedPermissionSlug {
|
|
6
|
+
/** Resource part (e.g., 'articles' from 'articles.create') */
|
|
7
|
+
resource: string;
|
|
8
|
+
/** Action part (e.g., 'create' from 'articles.create') */
|
|
9
|
+
action: string;
|
|
10
|
+
/** Whether the slug follows the valid 'resource.action' pattern */
|
|
11
|
+
isValid: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse a permission slug into its resource and action components.
|
|
15
|
+
* Valid format: 'resource.action' (e.g., 'articles.create')
|
|
16
|
+
*
|
|
17
|
+
* @param slug - The permission slug to parse
|
|
18
|
+
* @returns Parsed components with validation status
|
|
19
|
+
*/
|
|
20
|
+
export declare function parsePermissionSlug(slug: string): ParsedPermissionSlug;
|
|
21
|
+
/**
|
|
22
|
+
* Validate that a permission slug follows the 'resource.action' pattern.
|
|
23
|
+
*
|
|
24
|
+
* @param slug - The slug to validate
|
|
25
|
+
* @returns true if valid, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
export declare function isValidPermissionSlug(slug: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Permission represents a named capability in the system.
|
|
30
|
+
*
|
|
31
|
+
* Permissions are defined by the application and assigned to roles.
|
|
32
|
+
* Permission slugs follow the pattern: resource.action (e.g., 'articles.create')
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const permission = await permissions.create({
|
|
37
|
+
* slug: 'articles.create',
|
|
38
|
+
* name: 'Create Articles',
|
|
39
|
+
* description: 'Allows creating new articles',
|
|
40
|
+
* category: 'articles'
|
|
41
|
+
* });
|
|
42
|
+
* await permission.save();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class Permission extends SmrtObject {
|
|
46
|
+
/**
|
|
47
|
+
* Display name for the permission
|
|
48
|
+
*/
|
|
49
|
+
name: string;
|
|
50
|
+
/**
|
|
51
|
+
* Description of what this permission allows
|
|
52
|
+
*/
|
|
53
|
+
description: string;
|
|
54
|
+
/**
|
|
55
|
+
* Category for grouping in UI (e.g., 'articles', 'users', 'settings')
|
|
56
|
+
*/
|
|
57
|
+
category: string;
|
|
58
|
+
constructor(options?: any);
|
|
59
|
+
/**
|
|
60
|
+
* Parse the permission slug into resource and action components.
|
|
61
|
+
* @returns Parsed slug with resource, action, and validation status
|
|
62
|
+
*/
|
|
63
|
+
parseSlug(): ParsedPermissionSlug;
|
|
64
|
+
/**
|
|
65
|
+
* Get the resource part of the permission slug
|
|
66
|
+
* e.g., 'articles.create' -> 'articles'
|
|
67
|
+
*/
|
|
68
|
+
getResource(): string;
|
|
69
|
+
/**
|
|
70
|
+
* Get the action part of the permission slug
|
|
71
|
+
* e.g., 'articles.create' -> 'create'
|
|
72
|
+
*/
|
|
73
|
+
getAction(): string;
|
|
74
|
+
/**
|
|
75
|
+
* Check if the permission slug is valid (follows 'resource.action' pattern)
|
|
76
|
+
*/
|
|
77
|
+
isValidSlug(): boolean;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=Permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../../src/models/Permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CActE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAOa,UAAW,SAAQ,UAAU;IACxC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;gBAEV,OAAO,GAAE,GAAQ;IAQ7B;;;OAGG;IACH,SAAS,IAAI,oBAAoB;IAIjC;;;OAGG;IACH,WAAW,IAAI,MAAM;IAIrB;;;OAGG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { Role as RoleContract } from '@happyvertical/smrt-types';
|
|
3
|
+
/**
|
|
4
|
+
* Role represents a permission template that can be assigned to users.
|
|
5
|
+
*
|
|
6
|
+
* Roles can be:
|
|
7
|
+
* - System roles (tenantId = null): Available to all tenants, cannot be modified
|
|
8
|
+
* - Custom roles (tenantId set): Specific to a tenant, can be customized
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // System role (tenantId = null)
|
|
13
|
+
* const adminRole = await roles.create({
|
|
14
|
+
* slug: 'admin',
|
|
15
|
+
* name: 'Administrator',
|
|
16
|
+
* isSystem: true
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Custom tenant role
|
|
20
|
+
* const editorRole = await roles.create({
|
|
21
|
+
* tenantId: tenant.id,
|
|
22
|
+
* slug: 'editor',
|
|
23
|
+
* name: 'Editor',
|
|
24
|
+
* description: 'Can edit content'
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class Role extends SmrtObject implements RoleContract {
|
|
29
|
+
/**
|
|
30
|
+
* Foreign key to Tenant
|
|
31
|
+
* null = system role available to all tenants
|
|
32
|
+
*/
|
|
33
|
+
tenantId?: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Display name for the role
|
|
36
|
+
*/
|
|
37
|
+
name: string;
|
|
38
|
+
/**
|
|
39
|
+
* Description of the role
|
|
40
|
+
*/
|
|
41
|
+
description: string;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this is a system role (cannot be deleted)
|
|
44
|
+
*/
|
|
45
|
+
isSystem: boolean;
|
|
46
|
+
constructor(options?: any);
|
|
47
|
+
/**
|
|
48
|
+
* Check if this is a system-wide role
|
|
49
|
+
*/
|
|
50
|
+
isSystemRole(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Check if this is a tenant-specific role
|
|
53
|
+
*/
|
|
54
|
+
isTenantRole(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Check if this role can be deleted.
|
|
57
|
+
* System roles (isSystem = true) cannot be deleted.
|
|
58
|
+
* @returns true if the role can be deleted
|
|
59
|
+
*/
|
|
60
|
+
canDelete(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Delete guard - prevents deletion of system roles.
|
|
63
|
+
* Override the delete method to check isSystem flag first.
|
|
64
|
+
*/
|
|
65
|
+
delete(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=Role.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Role.d.ts","sourceRoot":"","sources":["../../src/models/Role.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAOa,IAAK,SAAQ,UAAW,YAAW,YAAY;IAC1D;;;OAGG;IAEH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAS;gBAEd,OAAO,GAAE,GAAQ;IAS7B;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;;;OAIG;IACH,SAAS,IAAI,OAAO;IAIpB;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ9B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* RolePermission is a join table linking Roles to Permissions.
|
|
4
|
+
*
|
|
5
|
+
* This enables many-to-many relationship between roles and permissions.
|
|
6
|
+
* A role can have multiple permissions, and a permission can belong to multiple roles.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Assign a permission to a role
|
|
11
|
+
* const rolePermission = await rolePermissions.create({
|
|
12
|
+
* roleId: adminRole.id,
|
|
13
|
+
* permissionId: createArticlePermission.id
|
|
14
|
+
* });
|
|
15
|
+
* await rolePermission.save();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class RolePermission extends SmrtObject {
|
|
19
|
+
/**
|
|
20
|
+
* Foreign key to Role
|
|
21
|
+
*/
|
|
22
|
+
roleId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Foreign key to Permission
|
|
25
|
+
*/
|
|
26
|
+
permissionId?: string;
|
|
27
|
+
constructor(options?: any);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=RolePermission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RolePermission.d.ts","sourceRoot":"","sources":["../../src/models/RolePermission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAExE;;;;;;;;;;;;;;;GAeG;AACH,qBAQa,cAAe,SAAQ,UAAU;IAC5C;;OAEG;IAEH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IAEH,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEV,OAAO,GAAE,GAAQ;CAM9B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { SessionStatus } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default session TTL: 7 days in seconds
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_SESSION_TTL: number;
|
|
7
|
+
/**
|
|
8
|
+
* Generate a cryptographically secure session ID
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateSessionId(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Session represents an authenticated user session.
|
|
13
|
+
*
|
|
14
|
+
* Sessions are stored server-side and linked to users.
|
|
15
|
+
* A session ID is stored in a cookie and used to look up the session.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const session = await sessions.create({
|
|
20
|
+
* userId: user.id,
|
|
21
|
+
* tenantId: tenant.id,
|
|
22
|
+
* expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
23
|
+
* userAgent: request.headers.get('user-agent'),
|
|
24
|
+
* ipAddress: getClientAddress(event)
|
|
25
|
+
* });
|
|
26
|
+
* await session.save();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class Session extends SmrtObject {
|
|
30
|
+
/**
|
|
31
|
+
* User who owns this session
|
|
32
|
+
*/
|
|
33
|
+
userId: string;
|
|
34
|
+
/**
|
|
35
|
+
* Tenant context for this session (for multi-tenant apps)
|
|
36
|
+
* Null means no tenant context selected
|
|
37
|
+
*/
|
|
38
|
+
tenantId: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Session status
|
|
41
|
+
*/
|
|
42
|
+
status: SessionStatus;
|
|
43
|
+
/**
|
|
44
|
+
* Session expiration time
|
|
45
|
+
*/
|
|
46
|
+
expiresAt: Date;
|
|
47
|
+
/**
|
|
48
|
+
* User agent string from the browser
|
|
49
|
+
*/
|
|
50
|
+
userAgent: string;
|
|
51
|
+
/**
|
|
52
|
+
* IP address of the client
|
|
53
|
+
*/
|
|
54
|
+
ipAddress: string;
|
|
55
|
+
/**
|
|
56
|
+
* Last activity timestamp (updated on each request)
|
|
57
|
+
*/
|
|
58
|
+
lastAccessedAt: Date;
|
|
59
|
+
/**
|
|
60
|
+
* Custom session data (JSON serializable)
|
|
61
|
+
*/
|
|
62
|
+
data: Record<string, unknown>;
|
|
63
|
+
constructor(options?: any);
|
|
64
|
+
/**
|
|
65
|
+
* Check if the session is currently valid (active and not expired)
|
|
66
|
+
*/
|
|
67
|
+
isValid(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Check if the session has expired
|
|
70
|
+
*/
|
|
71
|
+
isExpired(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Check if the session was revoked
|
|
74
|
+
*/
|
|
75
|
+
isRevoked(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Update the last accessed timestamp
|
|
78
|
+
*/
|
|
79
|
+
touch(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Extend the session expiration by the given TTL (in seconds)
|
|
82
|
+
*/
|
|
83
|
+
extend(ttlSeconds?: number): void;
|
|
84
|
+
/**
|
|
85
|
+
* Revoke the session
|
|
86
|
+
*/
|
|
87
|
+
revoke(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Set the tenant context for this session
|
|
90
|
+
*/
|
|
91
|
+
setTenant(tenantId: string | null): void;
|
|
92
|
+
/**
|
|
93
|
+
* Get or set custom session data
|
|
94
|
+
*/
|
|
95
|
+
getData<T>(key: string): T | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Set custom session data
|
|
98
|
+
*/
|
|
99
|
+
setData(key: string, value: unknown): void;
|
|
100
|
+
/**
|
|
101
|
+
* Remove custom session data
|
|
102
|
+
*/
|
|
103
|
+
removeData(key: string): void;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=Session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../../src/models/Session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAmB,CAAC;AAEpD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAMa,OAAQ,SAAQ,UAAU;IACrC;;OAEG;IAEH,MAAM,EAAE,MAAM,CAAM;IAEpB;;;OAGG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,MAAM,EAAE,aAAa,CAAwB;IAE7C;;OAEG;IACH,SAAS,EAAE,IAAI,CAAc;IAE7B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,cAAc,EAAE,IAAI,CAAc;IAElC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;gBAEvB,OAAO,GAAE,GAAQ;IAsB7B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,MAAM,CAAC,UAAU,GAAE,MAA4B,GAAG,IAAI;IAKtD;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIxC;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAItC;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAI1C;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG9B"}
|