cauthy-auth 0.28.1 → 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/README.md +51 -0
- package/dist/{cauthy-CTVg4kvo.d.mts → cauthy-BXgsZ-Um.d.mts} +19 -1
- package/dist/{cauthy-BAwAJChs.d.ts → cauthy-CHENs452.d.ts} +19 -1
- package/dist/cli.js +155 -54
- package/dist/client.d.mts +47 -7
- package/dist/client.d.ts +47 -7
- package/dist/client.js +23 -1
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +23 -1
- package/dist/client.mjs.map +1 -1
- package/dist/express.d.mts +2 -2
- package/dist/express.d.ts +2 -2
- package/dist/fastify.d.mts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/hono.d.mts +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +325 -129
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +323 -129
- package/dist/index.mjs.map +1 -1
- package/dist/next.d.mts +2 -2
- package/dist/next.d.ts +2 -2
- package/dist/react.d.mts +214 -18
- package/dist/react.d.ts +214 -18
- package/dist/react.js +129 -32
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +129 -32
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.d.mts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +37 -2
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +37 -2
- package/dist/svelte.mjs.map +1 -1
- package/dist/{system-CVBW_AV8.d.mts → system-0mHrdAgO.d.mts} +307 -109
- package/dist/{system-Bn8uckvQ.d.ts → system-BIYbS7h2.d.ts} +307 -109
- package/dist/vue.d.mts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +37 -2
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +37 -2
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ interface Permitted {
|
|
|
85
85
|
permissions?: string[];
|
|
86
86
|
/** Entries the person is denied whatever else allows them. */
|
|
87
87
|
denied?: string[];
|
|
88
|
+
/** Permissions with a condition: in neither list, so `can` never counts them. Ask with `auth.check`. */
|
|
89
|
+
conditional?: ConditionalPermission[];
|
|
88
90
|
}
|
|
89
91
|
/**
|
|
90
92
|
* Whether the person may do an action on a resource in an organization:
|
|
@@ -93,6 +95,11 @@ interface Permitted {
|
|
|
93
95
|
* An organization of a system that keeps no catalog in Cauthy carries no
|
|
94
96
|
* permissions, and the answer is `false`: decide by its `role` instead.
|
|
95
97
|
*
|
|
98
|
+
* A permission with a condition (`organization.conditional`) is not in the
|
|
99
|
+
* lists, so `can` answers `false` for what only it allows: the condition reads
|
|
100
|
+
* the record at hand, which only Cauthy can weigh. Ask with `auth.check`
|
|
101
|
+
* (`organizations.check` in the browser).
|
|
102
|
+
*
|
|
96
103
|
* if (!can(organization, 'invoicing', 'write')) return forbidden()
|
|
97
104
|
*/
|
|
98
105
|
declare function can(organization: Permitted | null | undefined, resource: string, action: string): boolean;
|
|
@@ -101,6 +108,21 @@ interface PolicyPermission {
|
|
|
101
108
|
kind?: string;
|
|
102
109
|
resource: string;
|
|
103
110
|
action: string;
|
|
111
|
+
/**
|
|
112
|
+
* When it holds, in CEL: `resource.attrs.amount <= 1000000`. A permission
|
|
113
|
+
* with one is decided by Cauthy per question (`auth.check`), never by `can`.
|
|
114
|
+
*/
|
|
115
|
+
condition?: string;
|
|
116
|
+
}
|
|
117
|
+
/** The type of an attribute a condition may read: `list` is any JSON array. */
|
|
118
|
+
type AttributeType = 'string' | 'number' | 'bool' | 'list';
|
|
119
|
+
/** A resource of a system's catalog: the actions done on it and the attributes its conditions read. */
|
|
120
|
+
interface CatalogResource {
|
|
121
|
+
resource: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
actions: string[];
|
|
124
|
+
/** Name → type, as `resource.attrs.<name>` in a condition. */
|
|
125
|
+
attributes?: Record<string, AttributeType>;
|
|
104
126
|
}
|
|
105
127
|
interface PolicyRole {
|
|
106
128
|
role: string;
|
|
@@ -110,17 +132,28 @@ interface PolicyRole {
|
|
|
110
132
|
inherits?: string[];
|
|
111
133
|
permissions?: PolicyPermission[];
|
|
112
134
|
}
|
|
135
|
+
type PermissionShape = {
|
|
136
|
+
readonly kind?: string;
|
|
137
|
+
readonly resource: string;
|
|
138
|
+
readonly action: string;
|
|
139
|
+
readonly condition?: string;
|
|
140
|
+
};
|
|
113
141
|
/** A role as `definePolicy` reads it: any `PolicyRole`, kept as written. */
|
|
114
142
|
type RoleShape = {
|
|
115
143
|
readonly role: string;
|
|
116
144
|
readonly name?: string;
|
|
117
145
|
readonly description?: string;
|
|
118
146
|
readonly inherits?: readonly string[];
|
|
119
|
-
readonly permissions?: readonly
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
147
|
+
readonly permissions?: readonly PermissionShape[];
|
|
148
|
+
};
|
|
149
|
+
/** A resource as `definePolicy` reads it: any `CatalogResource`, kept as written. */
|
|
150
|
+
type ResourceShape = {
|
|
151
|
+
readonly resource: string;
|
|
152
|
+
readonly description?: string;
|
|
153
|
+
readonly actions: readonly string[];
|
|
154
|
+
readonly attributes?: {
|
|
155
|
+
readonly [name: string]: AttributeType;
|
|
156
|
+
};
|
|
124
157
|
};
|
|
125
158
|
type Named<S extends string> = S extends '*' ? string : S;
|
|
126
159
|
type PermissionsIn<R> = R extends {
|
|
@@ -129,13 +162,56 @@ type PermissionsIn<R> = R extends {
|
|
|
129
162
|
readonly resource: infer Res extends string;
|
|
130
163
|
readonly action: infer Act extends string;
|
|
131
164
|
} ? `${Named<Res>}:${Named<Act>}` : never : never;
|
|
165
|
+
type ValueOf<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'bool' ? boolean : T extends 'list' ? unknown[] : never;
|
|
166
|
+
/** What a check may say about one declared resource. */
|
|
167
|
+
type QuestionAbout<E> = E extends {
|
|
168
|
+
readonly resource: infer R extends string;
|
|
169
|
+
readonly actions: readonly (infer A extends string)[];
|
|
170
|
+
} ? {
|
|
171
|
+
resource: R;
|
|
172
|
+
action: A;
|
|
173
|
+
attributes?: E extends {
|
|
174
|
+
readonly attributes: infer T;
|
|
175
|
+
} ? {
|
|
176
|
+
[K in keyof T]?: ValueOf<T[K]>;
|
|
177
|
+
} : Record<string, never>;
|
|
178
|
+
resourceId?: string;
|
|
179
|
+
ownerId?: string;
|
|
180
|
+
} : never;
|
|
181
|
+
/**
|
|
182
|
+
* What a role of a catalog that declares its resources may name: a declared
|
|
183
|
+
* resource with one of its actions, or "*", or one of Cauthy's own powers.
|
|
184
|
+
*/
|
|
185
|
+
type PermissionFor<C extends readonly ResourceShape[]> = {
|
|
186
|
+
readonly kind?: string;
|
|
187
|
+
readonly condition?: string;
|
|
188
|
+
} & ((C[number] extends infer E ? E extends {
|
|
189
|
+
readonly resource: infer R extends string;
|
|
190
|
+
readonly actions: readonly (infer A extends string)[];
|
|
191
|
+
} ? {
|
|
192
|
+
readonly resource: R;
|
|
193
|
+
readonly action: A | '*';
|
|
194
|
+
} : never : never) | {
|
|
195
|
+
readonly resource: '*';
|
|
196
|
+
readonly action: string;
|
|
197
|
+
} | {
|
|
198
|
+
readonly resource: `cauthy:${string}`;
|
|
199
|
+
readonly action: string;
|
|
200
|
+
});
|
|
201
|
+
type RoleFor<C extends readonly ResourceShape[]> = Omit<RoleShape, 'permissions'> & {
|
|
202
|
+
readonly permissions?: readonly PermissionFor<C>[];
|
|
203
|
+
};
|
|
132
204
|
/** The roles a catalog names: `RoleOf<typeof policy>`. */
|
|
133
|
-
type RoleOf<P> = P extends Policy<infer R,
|
|
205
|
+
type RoleOf<P> = P extends Policy<infer R, any, any> ? R : never;
|
|
134
206
|
/** The "resource:action" permissions a catalog names: `PermissionOf<typeof policy>`. */
|
|
135
|
-
type PermissionOf<P> = P extends Policy<
|
|
136
|
-
|
|
207
|
+
type PermissionOf<P> = P extends Policy<any, infer X, any> ? X : never;
|
|
208
|
+
/** The fields of a question: whatever else the asker takes (the organization, the person) passes through. */
|
|
209
|
+
type QuestionKeys = keyof CheckQuestion;
|
|
210
|
+
interface Policy<Role extends string = string, Permission extends string = string, Question extends CheckQuestion = CheckQuestion> {
|
|
137
211
|
/** The catalog as Cauthy takes it: `pushPolicy({ roles: policy.roles })`. */
|
|
138
212
|
roles: PolicyRole[];
|
|
213
|
+
/** The resources the system declares, as Cauthy takes them; empty when it declares none. */
|
|
214
|
+
catalog: CatalogResource[];
|
|
139
215
|
/** Every role's name. */
|
|
140
216
|
roleNames: Role[];
|
|
141
217
|
/** Every permission some role has, as "resource:action". */
|
|
@@ -143,7 +219,8 @@ interface Policy<Role extends string = string, Permission extends string = strin
|
|
|
143
219
|
/**
|
|
144
220
|
* Whether the person may do this in the organization: `can(org, 'ledger:write')`.
|
|
145
221
|
* It reads what Cauthy sent with the session, so a catalog edited in Cauthy
|
|
146
|
-
* since this file was written is still what decides.
|
|
222
|
+
* since this file was written is still what decides. A permission with a
|
|
223
|
+
* condition is not there: ask with `check`.
|
|
147
224
|
*/
|
|
148
225
|
can(organization: Permitted | null | undefined, permission: Permission): boolean;
|
|
149
226
|
/** Whether the person's role in the organization is one of these. */
|
|
@@ -155,9 +232,30 @@ interface Policy<Role extends string = string, Permission extends string = strin
|
|
|
155
232
|
* role, never for deciding about a person (their exceptions are not here).
|
|
156
233
|
*/
|
|
157
234
|
permissionsOf(role: Role): Permission[];
|
|
235
|
+
/**
|
|
236
|
+
* Asks Cauthy, with the resource, action and attributes typed by the
|
|
237
|
+
* catalog. Give it whoever asks: the server's `auth` with the request,
|
|
238
|
+
* the browser's `organizations`, or the application's `system`.
|
|
239
|
+
*
|
|
240
|
+
* await policy.check(auth, request, { organization, resource: 'invoices', action: 'approve', attributes: { amount: 500 } })
|
|
241
|
+
* await policy.check(auth.system, { organizationId, userId, resource: 'invoices', action: 'read' })
|
|
242
|
+
*/
|
|
243
|
+
check<I extends CheckQuestion>(asker: {
|
|
244
|
+
check(input: I): Promise<CheckResult>;
|
|
245
|
+
}, input: Omit<I, QuestionKeys> & Question): Promise<CheckResult>;
|
|
246
|
+
check<R, I extends CheckQuestion>(asker: {
|
|
247
|
+
check(request: R, input: I): Promise<CheckResult>;
|
|
248
|
+
}, request: R, input: Omit<I, QuestionKeys> & Question): Promise<CheckResult>;
|
|
249
|
+
}
|
|
250
|
+
/** A catalog with its resources declared, for `definePolicy`. */
|
|
251
|
+
interface PolicyDefinition<C extends readonly ResourceShape[], T extends readonly RoleShape[]> {
|
|
252
|
+
catalog: C;
|
|
253
|
+
roles: T;
|
|
158
254
|
}
|
|
159
255
|
/** Gives a catalog of roles its types. See the top of this section. */
|
|
160
256
|
declare function definePolicy<const T extends readonly RoleShape[]>(roles: T): Policy<T[number]['role'], PermissionsIn<T[number]>>;
|
|
257
|
+
/** Gives a catalog of resources and roles its types: a role names only what the catalog declares. */
|
|
258
|
+
declare function definePolicy<const C extends readonly ResourceShape[], const T extends readonly RoleFor<C>[]>(definition: PolicyDefinition<C, T>): Policy<T[number]['role'], PermissionsIn<T[number]>, QuestionAbout<C[number]>>;
|
|
161
259
|
|
|
162
260
|
/** The person signed in. Every moment is UTC. */
|
|
163
261
|
interface SessionUser {
|
|
@@ -200,6 +298,54 @@ interface SessionOrganization {
|
|
|
200
298
|
permissions?: string[];
|
|
201
299
|
/** What the person is denied here, whatever else allows it. */
|
|
202
300
|
denied?: string[];
|
|
301
|
+
/**
|
|
302
|
+
* Permissions that hold only when their condition does (an amount under a
|
|
303
|
+
* limit, the person's own records…). They are in neither list above: `can`
|
|
304
|
+
* answers no for them, and `auth.check` asks Cauthy. Present, maybe empty,
|
|
305
|
+
* whenever `permissions` is.
|
|
306
|
+
*/
|
|
307
|
+
conditional?: ConditionalPermission[];
|
|
308
|
+
}
|
|
309
|
+
/** A permission with a condition: Cauthy decides it per question (`auth.check`). */
|
|
310
|
+
interface ConditionalPermission {
|
|
311
|
+
resource: string;
|
|
312
|
+
action: string;
|
|
313
|
+
effect: 'allow' | 'deny';
|
|
314
|
+
}
|
|
315
|
+
/** What to ask Cauthy about: an action on a resource, with what the conditions read of it. */
|
|
316
|
+
interface CheckQuestion {
|
|
317
|
+
resource: string;
|
|
318
|
+
action: string;
|
|
319
|
+
/** What a condition reads as `resource.attrs`: `{ amount: 500 }`. */
|
|
320
|
+
attributes?: Record<string, unknown>;
|
|
321
|
+
/** The record's id, as a condition reads it (`resource.id`). */
|
|
322
|
+
resourceId?: string;
|
|
323
|
+
/** Whose the record is (`resource.owner_id`): `resource.owner_id == subject.id`. */
|
|
324
|
+
ownerId?: string;
|
|
325
|
+
}
|
|
326
|
+
/** An organization, by its id or as the session has it. */
|
|
327
|
+
type OrganizationRef = string | {
|
|
328
|
+
id: string;
|
|
329
|
+
};
|
|
330
|
+
/** The rule that decided a check. */
|
|
331
|
+
interface CheckRule {
|
|
332
|
+
/** A permission of the person's role, or an exception given to the person. */
|
|
333
|
+
source: 'role' | 'exception';
|
|
334
|
+
/** The role, when `source` is `role`. */
|
|
335
|
+
role?: string;
|
|
336
|
+
kind: string;
|
|
337
|
+
resource: string;
|
|
338
|
+
action: string;
|
|
339
|
+
effect: 'allow' | 'deny';
|
|
340
|
+
condition?: string;
|
|
341
|
+
}
|
|
342
|
+
/** Cauthy's answer to a check. */
|
|
343
|
+
interface CheckResult {
|
|
344
|
+
allowed: boolean;
|
|
345
|
+
/** Why, in words: "allowed by role accountant: invoices:approve". */
|
|
346
|
+
reason: string;
|
|
347
|
+
/** The rule that decided; absent when none did (nothing allows it). */
|
|
348
|
+
rule?: CheckRule;
|
|
203
349
|
}
|
|
204
350
|
/** What the browser is told of a session: who, and their organizations — never a token. */
|
|
205
351
|
interface PublicSession {
|
|
@@ -385,19 +531,29 @@ interface ConnectedAgent {
|
|
|
385
531
|
connectedAt: string;
|
|
386
532
|
lastUsedAt: string;
|
|
387
533
|
}
|
|
388
|
-
/** An organization the person belongs to, with what their role lets them do with its people. */
|
|
534
|
+
/** An organization the person belongs to, with what their role lets them do with it and its people. */
|
|
389
535
|
interface OrganizationSeat {
|
|
390
536
|
id: string;
|
|
391
|
-
/** The client system it is of (`
|
|
537
|
+
/** The client system it is of (`acme-crm`). */
|
|
392
538
|
system: string;
|
|
393
539
|
name: string;
|
|
394
540
|
kind: string;
|
|
395
541
|
/** The person's role in it. */
|
|
396
542
|
role: string;
|
|
397
|
-
/**
|
|
543
|
+
/**
|
|
544
|
+
* Cauthy's reserved powers, from the system's catalog. `manageMembers`
|
|
545
|
+
* (cauthy:members:manage) adds, invites and changes roles; removing another
|
|
546
|
+
* member is `removeMembers` (cauthy:members:remove), and anybody may leave.
|
|
547
|
+
* `update`, `archive` and `delete` are cauthy:organization's: rename it,
|
|
548
|
+
* archive it, delete it for good.
|
|
549
|
+
*/
|
|
398
550
|
can: {
|
|
399
551
|
readMembers: boolean;
|
|
400
552
|
manageMembers: boolean;
|
|
553
|
+
removeMembers: boolean;
|
|
554
|
+
update: boolean;
|
|
555
|
+
archive: boolean;
|
|
556
|
+
delete: boolean;
|
|
401
557
|
};
|
|
402
558
|
}
|
|
403
559
|
/** A person of an organization, as a member who may see them reads it. */
|
|
@@ -427,8 +583,12 @@ interface OrganizationRoleChoice {
|
|
|
427
583
|
}
|
|
428
584
|
/**
|
|
429
585
|
* The signed-in person's organizations, and their people. Seeing them needs
|
|
430
|
-
* cauthy:members read in the person's role; changing them needs manage
|
|
431
|
-
*
|
|
586
|
+
* cauthy:members read in the person's role; changing them needs manage, and
|
|
587
|
+
* removing someone needs remove. The organization itself is renamed, archived
|
|
588
|
+
* or deleted with cauthy:organization update, archive or delete. Only an
|
|
589
|
+
* `owner` gives the owner role or changes an owner, and the last owner stays
|
|
590
|
+
* (`last_owner`). Cauthy decides, with the person's own session: the
|
|
591
|
+
* application authorizes nothing.
|
|
432
592
|
*/
|
|
433
593
|
interface OrganizationsClient {
|
|
434
594
|
/** The organizations the person belongs to; one system's only with `system`. */
|
|
@@ -460,6 +620,31 @@ interface OrganizationsClient {
|
|
|
460
620
|
}): Promise<AuthResult<'sent', {
|
|
461
621
|
userId: string;
|
|
462
622
|
}>>;
|
|
623
|
+
/** Gives the organization a new name (cauthy:organization update). */
|
|
624
|
+
rename(organizationId: string, name: string): Promise<AuthResult<'renamed', {
|
|
625
|
+
organization: OrganizationSeat;
|
|
626
|
+
}>>;
|
|
627
|
+
/** Archives the organization: its members no longer reach it, and only the workspace restores it. */
|
|
628
|
+
archive(organizationId: string): Promise<AuthResult<'archived', {
|
|
629
|
+
organizationId: string;
|
|
630
|
+
}>>;
|
|
631
|
+
/** Deletes the organization for good. */
|
|
632
|
+
delete(organizationId: string): Promise<AuthResult<'deleted', {
|
|
633
|
+
organizationId: string;
|
|
634
|
+
}>>;
|
|
635
|
+
/**
|
|
636
|
+
* Asks Cauthy, through the application's server, whether the person may do
|
|
637
|
+
* an action on a resource in the organization, with what its conditions
|
|
638
|
+
* read. A question that could not be asked is a no, with `error` saying why.
|
|
639
|
+
*
|
|
640
|
+
* const r = await organizations.check({ organization, resource: 'invoices', action: 'approve', attributes: { amount: 500 } })
|
|
641
|
+
* if (r.allowed) showApprove()
|
|
642
|
+
*/
|
|
643
|
+
check(question: CheckQuestion & {
|
|
644
|
+
organization: OrganizationRef;
|
|
645
|
+
}): Promise<CheckResult & {
|
|
646
|
+
error?: AuthError;
|
|
647
|
+
}>;
|
|
463
648
|
}
|
|
464
649
|
interface AccountSession {
|
|
465
650
|
/** "Mac · Chrome", or "" when the browser did not say. */
|
|
@@ -1027,6 +1212,15 @@ interface AuthTexts {
|
|
|
1027
1212
|
memberLeaveConfirm: string;
|
|
1028
1213
|
memberInviteAgain: string;
|
|
1029
1214
|
memberInvitationSent: string;
|
|
1215
|
+
organization: string;
|
|
1216
|
+
organizationRename: string;
|
|
1217
|
+
organizationRenamed: string;
|
|
1218
|
+
organizationArchive: string;
|
|
1219
|
+
organizationArchiveConfirm: string;
|
|
1220
|
+
organizationArchived: string;
|
|
1221
|
+
organizationDelete: string;
|
|
1222
|
+
organizationDeleteConfirm: string;
|
|
1223
|
+
organizationDeleted: string;
|
|
1030
1224
|
yes: string;
|
|
1031
1225
|
cancel: string;
|
|
1032
1226
|
/** By error code; one missing shows what the server said. */
|
|
@@ -1144,10 +1338,12 @@ interface OrganizationMembersProps extends Omit<AuthFormProps, 'links' | 'return
|
|
|
1144
1338
|
* The people of one of the signed-in person's organizations, administered
|
|
1145
1339
|
* from the application. What it offers follows the person's role in the
|
|
1146
1340
|
* system's catalog: cauthy:members read lists the people; manage adds them by
|
|
1147
|
-
* address (one with no account is invited), changes their role
|
|
1148
|
-
* invitation again
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1341
|
+
* address (one with no account is invited), changes their role and sends an
|
|
1342
|
+
* invitation again; remove removes them. Anybody may leave. Only an owner
|
|
1343
|
+
* gives the owner role or changes an owner. cauthy:organization update,
|
|
1344
|
+
* archive and delete rename the organization, archive it, delete it. Cauthy
|
|
1345
|
+
* checks each change with the person's own session; the application
|
|
1346
|
+
* authorizes nothing. Like the other forms, it carries no styles.
|
|
1151
1347
|
*/
|
|
1152
1348
|
declare function OrganizationMembers(props: OrganizationMembersProps): react.JSX.Element;
|
|
1153
1349
|
|
package/dist/react.js
CHANGED
|
@@ -321,6 +321,12 @@ function checkOrganization(value, required = false) {
|
|
|
321
321
|
if (v.length > MAX_ORGANIZATION) return { field: "organization", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
|
|
322
322
|
return null;
|
|
323
323
|
}
|
|
324
|
+
function checkOrganizationName(value) {
|
|
325
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
326
|
+
if (v === "") return { field: "name", code: "required", message: "Enter the organization's name." };
|
|
327
|
+
if (v.length > MAX_ORGANIZATION) return { field: "name", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
324
330
|
function checkCode(value) {
|
|
325
331
|
const v = typeof value === "string" ? value.trim() : "";
|
|
326
332
|
if (v === "") return { field: "code", code: "required", message: "Enter the code." };
|
|
@@ -573,7 +579,23 @@ function createAuthClient(options = {}) {
|
|
|
573
579
|
add: (id, f) => post("/me/organizations/members/add", { organizationId: id, email: f.email, role: f.role, name: f.name ?? "", locale: f.locale ?? "" }, checkEmail(f.email)),
|
|
574
580
|
setRole: (id, userId, role) => post("/me/organizations/members/role", { organizationId: id, userId, role }, null),
|
|
575
581
|
remove: (id, userId) => post("/me/organizations/members/remove", { organizationId: id, userId }, null),
|
|
576
|
-
inviteAgain: (id, userId, f = {}) => post("/me/organizations/members/invite", { organizationId: id, userId, locale: f.locale ?? "" }, null)
|
|
582
|
+
inviteAgain: (id, userId, f = {}) => post("/me/organizations/members/invite", { organizationId: id, userId, locale: f.locale ?? "" }, null),
|
|
583
|
+
rename: (id, name) => post("/me/organizations/rename", { organizationId: id, name }, checkOrganizationName(name)),
|
|
584
|
+
archive: (id) => post("/me/organizations/archive", { organizationId: id }, null),
|
|
585
|
+
delete: (id) => post("/me/organizations/delete", { organizationId: id }, null),
|
|
586
|
+
async check(q) {
|
|
587
|
+
const organizationId = typeof q.organization === "string" ? q.organization : q.organization?.id ?? "";
|
|
588
|
+
const r = await post("/me/organizations/check", {
|
|
589
|
+
organizationId,
|
|
590
|
+
resource: q.resource,
|
|
591
|
+
action: q.action,
|
|
592
|
+
...q.attributes !== void 0 ? { attributes: q.attributes } : {},
|
|
593
|
+
...q.resourceId !== void 0 ? { resourceId: q.resourceId } : {},
|
|
594
|
+
...q.ownerId !== void 0 ? { ownerId: q.ownerId } : {}
|
|
595
|
+
}, null);
|
|
596
|
+
if (!r.ok) return { allowed: false, reason: r.error.message, error: r.error };
|
|
597
|
+
return { allowed: r.allowed === true, reason: String(r.reason ?? ""), ...r.rule ? { rule: r.rule } : {} };
|
|
598
|
+
}
|
|
577
599
|
},
|
|
578
600
|
account: {
|
|
579
601
|
async get() {
|
|
@@ -669,7 +691,9 @@ function can(organization, resource, action) {
|
|
|
669
691
|
if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false;
|
|
670
692
|
return organization.permissions.some((p) => matches(p, resource, action));
|
|
671
693
|
}
|
|
672
|
-
function definePolicy(
|
|
694
|
+
function definePolicy(given) {
|
|
695
|
+
const roles = Array.isArray(given) ? given : given.roles;
|
|
696
|
+
const resources = Array.isArray(given) ? [] : given.catalog ?? [];
|
|
673
697
|
const entry = (p) => `${p.resource}:${p.action}`;
|
|
674
698
|
const byName = new Map(roles.map((r) => [r.role, r]));
|
|
675
699
|
const split = (permission) => {
|
|
@@ -684,6 +708,12 @@ function definePolicy(roles) {
|
|
|
684
708
|
...r.inherits ? { inherits: [...r.inherits] } : {},
|
|
685
709
|
...r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}
|
|
686
710
|
})),
|
|
711
|
+
catalog: resources.map((c) => ({
|
|
712
|
+
resource: c.resource,
|
|
713
|
+
...c.description !== void 0 ? { description: c.description } : {},
|
|
714
|
+
actions: [...c.actions],
|
|
715
|
+
...c.attributes ? { attributes: { ...c.attributes } } : {}
|
|
716
|
+
})),
|
|
687
717
|
roleNames: roles.map((r) => r.role),
|
|
688
718
|
permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],
|
|
689
719
|
can(organization, permission) {
|
|
@@ -705,6 +735,11 @@ function definePolicy(roles) {
|
|
|
705
735
|
};
|
|
706
736
|
walk(role);
|
|
707
737
|
return [...out];
|
|
738
|
+
},
|
|
739
|
+
// The types did the work; this only hands the question on, as a method of
|
|
740
|
+
// the asker so it keeps its `this`.
|
|
741
|
+
check(asker, ...args) {
|
|
742
|
+
return asker.check(...args);
|
|
708
743
|
}
|
|
709
744
|
};
|
|
710
745
|
}
|
|
@@ -823,11 +858,22 @@ var TEXTS = {
|
|
|
823
858
|
memberLeaveConfirm: "Leave {org}? You will need an invitation to come back.",
|
|
824
859
|
memberInviteAgain: "Send the invitation again",
|
|
825
860
|
memberInvitationSent: "The invitation is on its way.",
|
|
861
|
+
organization: "Organization",
|
|
862
|
+
organizationRename: "Rename",
|
|
863
|
+
organizationRenamed: "The organization is now called {name}.",
|
|
864
|
+
organizationArchive: "Archive",
|
|
865
|
+
organizationArchiveConfirm: "Archive {org}? Its members will no longer reach it, and only the workspace can restore it.",
|
|
866
|
+
organizationArchived: "{org} was archived.",
|
|
867
|
+
organizationDelete: "Delete organization",
|
|
868
|
+
organizationDeleteConfirm: "Delete {org} for good? Its members lose it and this cannot be undone.",
|
|
869
|
+
organizationDeleted: "{org} was deleted.",
|
|
826
870
|
yes: "Yes",
|
|
827
871
|
cancel: "Cancel",
|
|
828
872
|
errors: {
|
|
829
873
|
not_allowed: "Your role in this organization does not allow that.",
|
|
830
874
|
last_manager: "Somebody else must manage the members first: give that role to another member.",
|
|
875
|
+
last_owner: "An organization keeps at least one owner: make another member owner first.",
|
|
876
|
+
organization_not_found: "That organization no longer exists.",
|
|
831
877
|
already_member: "That person already belongs to the organization. Change their role instead.",
|
|
832
878
|
unknown_role: "Choose one of the roles offered.",
|
|
833
879
|
user_not_active: "That account is disabled.",
|
|
@@ -979,11 +1025,22 @@ var TEXTS = {
|
|
|
979
1025
|
memberLeaveConfirm: "\xBFSalir de {org}? Necesitar\xE1s una invitaci\xF3n para volver.",
|
|
980
1026
|
memberInviteAgain: "Enviar la invitaci\xF3n de nuevo",
|
|
981
1027
|
memberInvitationSent: "La invitaci\xF3n va en camino.",
|
|
1028
|
+
organization: "Organizaci\xF3n",
|
|
1029
|
+
organizationRename: "Cambiar nombre",
|
|
1030
|
+
organizationRenamed: "La organizaci\xF3n ahora se llama {name}.",
|
|
1031
|
+
organizationArchive: "Archivar",
|
|
1032
|
+
organizationArchiveConfirm: "\xBFArchivar {org}? Sus miembros ya no podr\xE1n entrar y solo el espacio de trabajo puede restaurarla.",
|
|
1033
|
+
organizationArchived: "{org} qued\xF3 archivada.",
|
|
1034
|
+
organizationDelete: "Eliminar organizaci\xF3n",
|
|
1035
|
+
organizationDeleteConfirm: "\xBFEliminar {org} para siempre? Sus miembros la pierden y no se puede deshacer.",
|
|
1036
|
+
organizationDeleted: "{org} qued\xF3 eliminada.",
|
|
982
1037
|
yes: "S\xED",
|
|
983
1038
|
cancel: "Cancelar",
|
|
984
1039
|
errors: {
|
|
985
1040
|
not_allowed: "Tu rol en esta organizaci\xF3n no permite eso.",
|
|
986
1041
|
last_manager: "Primero alguien m\xE1s debe administrar los miembros: dale ese rol a otro miembro.",
|
|
1042
|
+
last_owner: "Una organizaci\xF3n conserva al menos un propietario: primero haz propietario a otro miembro.",
|
|
1043
|
+
organization_not_found: "Esa organizaci\xF3n ya no existe.",
|
|
987
1044
|
already_member: "Esa persona ya pertenece a la organizaci\xF3n. Cambia su rol.",
|
|
988
1045
|
unknown_role: "Escoge uno de los roles ofrecidos.",
|
|
989
1046
|
user_not_active: "Esa cuenta est\xE1 desactivada.",
|
|
@@ -1660,32 +1717,52 @@ function Account(props) {
|
|
|
1660
1717
|
] })
|
|
1661
1718
|
] });
|
|
1662
1719
|
}
|
|
1720
|
+
var OWNER = "owner";
|
|
1721
|
+
function memberActions(seat, m) {
|
|
1722
|
+
const reachable = m.role !== OWNER || seat.role === OWNER;
|
|
1723
|
+
return {
|
|
1724
|
+
changeRole: seat.can.manageMembers && reachable,
|
|
1725
|
+
// Leaving is anybody's; removing somebody else is cauthy:members:remove.
|
|
1726
|
+
remove: m.you || seat.can.removeMembers && reachable,
|
|
1727
|
+
inviteAgain: seat.can.manageMembers && m.invited
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function rolesToGive(seat, roles) {
|
|
1731
|
+
return seat.role === OWNER ? roles : roles.filter((c) => c.role !== OWNER);
|
|
1732
|
+
}
|
|
1663
1733
|
function OrganizationMembers(props) {
|
|
1664
1734
|
const auth = useAuth();
|
|
1665
1735
|
const { root } = useForm(props);
|
|
1666
1736
|
const { f, done, setDone, run } = useSection(props);
|
|
1667
|
-
const problem = f.error && f.error.field !== "email" ? f.error : null;
|
|
1737
|
+
const problem = f.error && f.error.field !== "email" && f.error.field !== "name" ? f.error : null;
|
|
1668
1738
|
const [seat, setSeat] = (0, import_react.useState)(void 0);
|
|
1669
1739
|
const [members, setMembers] = (0, import_react.useState)(null);
|
|
1670
1740
|
const [roles, setRoles] = (0, import_react.useState)([]);
|
|
1671
1741
|
const [email, setEmail] = (0, import_react.useState)("");
|
|
1672
1742
|
const [role, setRole] = (0, import_react.useState)("");
|
|
1743
|
+
const [name, setName] = (0, import_react.useState)("");
|
|
1673
1744
|
const [asking, setAsking] = (0, import_react.useState)(null);
|
|
1745
|
+
const [gone, setGone] = (0, import_react.useState)(null);
|
|
1674
1746
|
const id = props.organizationId;
|
|
1675
1747
|
const when = props.formatDate ?? ((iso) => new Date(iso).toLocaleDateString());
|
|
1676
1748
|
const load = async () => {
|
|
1677
1749
|
const [list, people, choices] = await Promise.all([auth.organizations.list(), auth.organizations.members(id), auth.organizations.roles(id)]);
|
|
1678
|
-
|
|
1750
|
+
const mine = list.find((o) => o.id === id) ?? null;
|
|
1751
|
+
setSeat(mine);
|
|
1752
|
+
if (mine) setName(mine.name);
|
|
1679
1753
|
setMembers(people);
|
|
1680
1754
|
setRoles(choices ?? []);
|
|
1681
|
-
|
|
1755
|
+
const offered = mine ? rolesToGive(mine, choices ?? []) : [];
|
|
1756
|
+
if (offered.length > 0 && !offered.some((c) => c.role === role)) setRole(offered[0].role);
|
|
1682
1757
|
};
|
|
1683
1758
|
(0, import_react.useEffect)(() => {
|
|
1684
1759
|
void load();
|
|
1685
1760
|
}, [id]);
|
|
1761
|
+
if (gone !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("account"), ...root, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: gone }) });
|
|
1686
1762
|
if (seat === void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("account"), ...root, "aria-busy": "true" });
|
|
1687
1763
|
if (seat === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("account"), ...root, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.organizationMissing }) });
|
|
1688
1764
|
const manage = seat.can.manageMembers;
|
|
1765
|
+
const giveable = rolesToGive(seat, roles);
|
|
1689
1766
|
const label = (r) => roles.find((c) => c.role === r)?.name || r;
|
|
1690
1767
|
const add = async (e) => {
|
|
1691
1768
|
e.preventDefault();
|
|
@@ -1710,6 +1787,25 @@ function OrganizationMembers(props) {
|
|
|
1710
1787
|
const r = await run(() => auth.organizations.inviteAgain(id, m.userId, { locale: props.locale }));
|
|
1711
1788
|
if (r.ok) setDone(f.t.memberInvitationSent);
|
|
1712
1789
|
};
|
|
1790
|
+
const rename = async (e) => {
|
|
1791
|
+
e.preventDefault();
|
|
1792
|
+
const r = await run(() => auth.organizations.rename(id, name));
|
|
1793
|
+
if (!r.ok) return;
|
|
1794
|
+
setSeat(r.organization);
|
|
1795
|
+
setName(r.organization.name);
|
|
1796
|
+
setDone(f.t.organizationRenamed.replace("{name}", r.organization.name));
|
|
1797
|
+
};
|
|
1798
|
+
const close = async (how) => {
|
|
1799
|
+
setAsking(null);
|
|
1800
|
+
const r = await run(() => how === "archive" ? auth.organizations.archive(id) : auth.organizations.delete(id));
|
|
1801
|
+
if (r.ok) setGone((how === "archive" ? f.t.organizationArchived : f.t.organizationDeleted).replace("{org}", seat.name));
|
|
1802
|
+
};
|
|
1803
|
+
const confirming = (text2, label2, yes) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { role: "group", "aria-label": label2, children: [
|
|
1804
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: text2 }),
|
|
1805
|
+
" ",
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: yes, children: f.t.yes }),
|
|
1807
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(null), children: f.t.cancel })
|
|
1808
|
+
] });
|
|
1713
1809
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("account"), ...root, children: [
|
|
1714
1810
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: f.cx("section"), children: [
|
|
1715
1811
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { className: f.cx("heading"), children: [
|
|
@@ -1720,23 +1816,14 @@ function OrganizationMembers(props) {
|
|
|
1720
1816
|
problem && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(problem) }),
|
|
1721
1817
|
members === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1722
1818
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.membersHidden }),
|
|
1723
|
-
auth.session && (asking === auth.session.user.id ?
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
{
|
|
1729
|
-
type: "button",
|
|
1730
|
-
disabled: f.busy,
|
|
1731
|
-
className: f.cx("providerButton"),
|
|
1732
|
-
onClick: () => void remove({ userId: auth.session.user.id, you: true }),
|
|
1733
|
-
children: f.t.yes
|
|
1734
|
-
}
|
|
1735
|
-
),
|
|
1736
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(null), children: f.t.cancel })
|
|
1737
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(auth.session.user.id), children: f.t.memberLeave }))
|
|
1819
|
+
auth.session && (asking === auth.session.user.id ? confirming(
|
|
1820
|
+
f.t.memberLeaveConfirm.replace("{org}", seat.name),
|
|
1821
|
+
f.t.memberLeave,
|
|
1822
|
+
() => void remove({ userId: auth.session.user.id, you: true })
|
|
1823
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(auth.session.user.id), children: f.t.memberLeave }))
|
|
1738
1824
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: f.cx("sessions"), children: members.map((m) => {
|
|
1739
1825
|
const who = m.name || m.email;
|
|
1826
|
+
const may = memberActions(seat, m);
|
|
1740
1827
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: f.cx("session"), children: [
|
|
1741
1828
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1742
1829
|
who,
|
|
@@ -1748,7 +1835,7 @@ function OrganizationMembers(props) {
|
|
|
1748
1835
|
when(m.since)
|
|
1749
1836
|
] }),
|
|
1750
1837
|
" ",
|
|
1751
|
-
|
|
1838
|
+
may.changeRole && giveable.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1752
1839
|
"select",
|
|
1753
1840
|
{
|
|
1754
1841
|
"aria-label": `${f.t.memberRole}: ${who}`,
|
|
@@ -1756,31 +1843,41 @@ function OrganizationMembers(props) {
|
|
|
1756
1843
|
value: m.role,
|
|
1757
1844
|
disabled: f.busy,
|
|
1758
1845
|
onChange: (e) => void change(m, e.target.value),
|
|
1759
|
-
children: roles.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: c.role, children: c.name || c.role }, c.role))
|
|
1846
|
+
children: (giveable.some((c) => c.role === m.role) ? giveable : [...roles.filter((c) => c.role === m.role), ...giveable]).map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: c.role, children: c.name || c.role }, c.role))
|
|
1760
1847
|
}
|
|
1761
1848
|
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label(m.role) }),
|
|
1762
1849
|
" ",
|
|
1763
|
-
asking === m.userId ?
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
(manage || m.you) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(m.userId), children: m.you ? f.t.memberLeave : f.t.memberRemove })
|
|
1850
|
+
asking === m.userId ? confirming(
|
|
1851
|
+
m.you ? f.t.memberLeaveConfirm.replace("{org}", seat.name) : f.t.memberRemoveConfirm.replace("{who}", who),
|
|
1852
|
+
m.you ? f.t.memberLeave : f.t.memberRemove,
|
|
1853
|
+
() => void remove(m)
|
|
1854
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1855
|
+
may.inviteAgain && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void inviteAgain(m), children: f.t.memberInviteAgain }),
|
|
1856
|
+
may.remove && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(m.userId), children: m.you ? f.t.memberLeave : f.t.memberRemove })
|
|
1771
1857
|
] })
|
|
1772
1858
|
] }, m.userId);
|
|
1773
1859
|
}) }),
|
|
1774
1860
|
done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
|
|
1775
1861
|
] }),
|
|
1776
|
-
manage &&
|
|
1862
|
+
manage && giveable.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
|
|
1777
1863
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.memberAdd }),
|
|
1778
1864
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "off", required: true, onChange: setEmail }),
|
|
1779
1865
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("field"), children: [
|
|
1780
1866
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: f.cx("label"), children: f.t.memberRole }),
|
|
1781
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { name: "role", className: f.cx("input"), value: role, onChange: (e) => setRole(e.target.value), children:
|
|
1867
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { name: "role", className: f.cx("input"), value: role, onChange: (e) => setRole(e.target.value), children: giveable.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: c.role, children: c.name || c.role }, c.role)) })
|
|
1782
1868
|
] }),
|
|
1783
1869
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.memberAdd })
|
|
1870
|
+
] }),
|
|
1871
|
+
(seat.can.update || seat.can.archive || seat.can.delete) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: f.cx("section"), children: [
|
|
1872
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.organization }),
|
|
1873
|
+
seat.can.update && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: rename, noValidate: true, children: [
|
|
1874
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "organization", required: true, onChange: setName }),
|
|
1875
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.organizationRename })
|
|
1876
|
+
] }),
|
|
1877
|
+
asking === "archive" ? confirming(f.t.organizationArchiveConfirm.replace("{org}", seat.name), f.t.organizationArchive, () => void close("archive")) : asking === "delete" ? confirming(f.t.organizationDeleteConfirm.replace("{org}", seat.name), f.t.organizationDelete, () => void close("delete")) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1878
|
+
seat.can.archive && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking("archive"), children: f.t.organizationArchive }),
|
|
1879
|
+
seat.can.delete && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking("delete"), children: f.t.organizationDelete })
|
|
1880
|
+
] })
|
|
1784
1881
|
] })
|
|
1785
1882
|
] });
|
|
1786
1883
|
}
|