@tula-tech/contract 0.2.0 → 0.3.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 +106 -1
- package/dist/custom-claims.d.ts +118 -3
- package/dist/custom-claims.js +21 -2
- package/dist/error-codes.d.ts +204 -0
- package/dist/error-codes.js +1 -1
- package/dist/event-types.d.ts +34 -1
- package/dist/event-types.js +1 -1
- package/dist/headers.d.ts +25 -5
- package/dist/headers.js +7 -3
- package/dist/index.d.ts +3928 -788
- package/dist/index.js +1895 -596
- package/dist/organization-domain-rules.d.ts +132 -0
- package/dist/organization-domain-rules.js +34 -0
- package/dist/organization-permissions.d.ts +185 -0
- package/dist/organization-permissions.js +42 -0
- package/dist/password-rules.d.ts +204 -0
- package/dist/redirect-url.d.ts +3 -3
- package/dist/redirect-url.js +1 -1
- package/dist/shared/{chunk-q309e9bk.js → chunk-4hytq4je.js} +3 -1
- package/dist/shared/{chunk-cce3bhet.js → chunk-5r2ka03q.js} +192 -0
- package/dist/shared/{chunk-7yqzghgm.js → chunk-bsea97tx.js} +66 -0
- package/dist/shared/chunk-cdg41bxh.js +151 -0
- package/dist/shared/{chunk-4rb5jsfz.js → chunk-fkx6fc8e.js} +61 -1
- package/dist/shared/{chunk-r202nxfw.js → chunk-n1jdq72m.js} +18 -3
- package/dist/shared/chunk-yzgakhtb.js +50 -0
- package/openapi.json +29170 -10212
- package/package.json +10 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** Longest a whole domain can be, in characters (RFC 1035). */
|
|
2
|
+
declare const MAX_DOMAIN_LENGTH = 253;
|
|
3
|
+
/** Longest one label of a domain can be, in characters (RFC 1035). */
|
|
4
|
+
declare const MAX_DOMAIN_LABEL_LENGTH = 63;
|
|
5
|
+
/** Shortest a domain can be: `a.bc`. */
|
|
6
|
+
declare const MIN_DOMAIN_LENGTH = 4;
|
|
7
|
+
/**
|
|
8
|
+
* How many domains one organization can claim, verified or waiting for a code. A bound so that
|
|
9
|
+
* a signed-in administrator cannot fill the table.
|
|
10
|
+
*/
|
|
11
|
+
declare const MAX_DOMAINS_PER_ORGANIZATION = 10;
|
|
12
|
+
/**
|
|
13
|
+
* How long a verification of a domain lasts, in days.
|
|
14
|
+
* A domain can be lost and registered again by someone else, and nothing tells Tula, so a
|
|
15
|
+
* claim is only believed for this long: past it the domain matches nobody (no auto-join, no
|
|
16
|
+
* suggestion) until a manager proves it again with a new emailed code. Counted from the day
|
|
17
|
+
* the code was entered.
|
|
18
|
+
*/
|
|
19
|
+
declare const DOMAIN_VERIFICATION_LIFETIME_DAYS = 365;
|
|
20
|
+
/**
|
|
21
|
+
* How many days before a verification lapses the organization's managers are emailed, largest
|
|
22
|
+
* first. Each is sent once per verification: a reminder is never repeated, and re-verifying
|
|
23
|
+
* starts the series again.
|
|
24
|
+
*/
|
|
25
|
+
declare const DOMAIN_REMINDER_DAYS: readonly number[];
|
|
26
|
+
/**
|
|
27
|
+
* The least time, in days, that a domain verified before the lifetime existed (migration
|
|
28
|
+
* 0042) keeps matching after the migration: its deadline is the later of the original
|
|
29
|
+
* `verified_at` plus the lifetime and this many days from the migration, so that nobody is
|
|
30
|
+
* cut off the day the change ships and every manager gets a reminder before it ends.
|
|
31
|
+
*/
|
|
32
|
+
declare const DOMAIN_REVERIFICATION_GRACE_DAYS = 30;
|
|
33
|
+
/** What a domain row is: waiting for its first code, verified, or past its lifetime. */
|
|
34
|
+
declare const ORGANIZATION_DOMAIN_STATUSES: readonly ["pending", "verified", "needs_reverification"];
|
|
35
|
+
/** One of {@link ORGANIZATION_DOMAIN_STATUSES}. */
|
|
36
|
+
type OrganizationDomainStatus = (typeof ORGANIZATION_DOMAIN_STATUSES)[number];
|
|
37
|
+
/** How an organization treats a user who signs up with a verified address on its domain. */
|
|
38
|
+
declare const ORGANIZATION_DOMAIN_MODES: readonly ["suggest", "auto_join"];
|
|
39
|
+
/** One of {@link ORGANIZATION_DOMAIN_MODES}. */
|
|
40
|
+
type OrganizationDomainMode = (typeof ORGANIZATION_DOMAIN_MODES)[number];
|
|
41
|
+
/**
|
|
42
|
+
* The grammar of a claimable domain: two or more labels of lower-case ASCII letters, digits
|
|
43
|
+
* and hyphens (none at a label's end), joined by single full stops, the last label letters
|
|
44
|
+
* only (or punycode, `xn--…`). So: no upper case, no trailing full stop, no port or path, no
|
|
45
|
+
* underscore, no `@`, no wildcard, and no IP address (a numeric last label is refused, and so
|
|
46
|
+
* is the bracketed IPv6 form).
|
|
47
|
+
*
|
|
48
|
+
* **Internationalised domains are written in punycode** (`xn--bcher-kva.example`, never
|
|
49
|
+
* `bücher.example`): Unicode is refused, not converted, so a domain is stored and compared in
|
|
50
|
+
* the one spelling a mail server uses. The pattern is the grammar alone; the length bounds
|
|
51
|
+
* ({@link MAX_DOMAIN_LENGTH}, {@link MAX_DOMAIN_LABEL_LENGTH}) and the public list are
|
|
52
|
+
* {@link domainRefusal}'s. `domainRefusal` runs it only over text of at most twice the length cap,
|
|
53
|
+
* so its work is bounded.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* DOMAIN_PATTERN.test('acme.com') // true
|
|
58
|
+
* DOMAIN_PATTERN.test('Acme.com') // false
|
|
59
|
+
* DOMAIN_PATTERN.test('192.168.0.1') // false
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare const DOMAIN_PATTERN: RegExp;
|
|
63
|
+
/** Why a string is not a claimable domain. */
|
|
64
|
+
type DomainRefusal = "grammar" | "length" | "public";
|
|
65
|
+
/**
|
|
66
|
+
* Domains that anyone can get an address at, and that so prove nothing about the holder of an
|
|
67
|
+
* address on them. **Documented here and nowhere else**: a claim of one is refused
|
|
68
|
+
* (`organization.domain_public`) and a join never matches one, even a claim stored before the
|
|
69
|
+
* list grew. The list is the large free consumer mailbox providers, the internet providers
|
|
70
|
+
* that give every customer an address, and the common national ones; a company's own domain
|
|
71
|
+
* is never on it.
|
|
72
|
+
*
|
|
73
|
+
* Providers that sell a mailbox under many country domains (`yahoo.co.uk`, `yahoo.fr`,
|
|
74
|
+
* `hotmail.de`, …) are in {@link PUBLIC_EMAIL_BRANDS} instead.
|
|
75
|
+
*/
|
|
76
|
+
declare const PUBLIC_EMAIL_DOMAINS: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* Provider names whose every country domain is public: `yahoo.com`, `yahoo.co.uk`, `yahoo.fr`
|
|
79
|
+
* and `yahoo.co.jp` are all `yahoo`. A domain matches when its **first** label is one of
|
|
80
|
+
* these and what follows is a top-level domain, or a short second-level one and a top-level
|
|
81
|
+
* one (`co.uk`, `com.br`): `yahoo.com` and `hotmail.co.uk` match, `yahoo.acme.com` and
|
|
82
|
+
* `notyahoo.com` do not.
|
|
83
|
+
*/
|
|
84
|
+
declare const PUBLIC_EMAIL_BRANDS: readonly string[];
|
|
85
|
+
/**
|
|
86
|
+
* Whether anyone can get an address on this domain: it is on {@link PUBLIC_EMAIL_DOMAINS} or
|
|
87
|
+
* is a country domain of one of {@link PUBLIC_EMAIL_BRANDS}.
|
|
88
|
+
*
|
|
89
|
+
* @param domain - A domain in the spelling of {@link DOMAIN_PATTERN}.
|
|
90
|
+
* @returns `true` for a public mail domain.
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* isPublicEmailDomain('gmail.com') // true
|
|
94
|
+
* isPublicEmailDomain('yahoo.co.uk') // true
|
|
95
|
+
* isPublicEmailDomain('acme.com') // false
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare function isPublicEmailDomain(domain: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Why a string cannot be claimed as a domain, or `null` when it can.
|
|
101
|
+
*
|
|
102
|
+
* @param domain - The text to judge.
|
|
103
|
+
* @returns `grammar` (not a lower-case ASCII domain name: upper case, Unicode, an IP address,
|
|
104
|
+
* a single label), `length` (a label over {@link MAX_DOMAIN_LABEL_LENGTH}, the whole over
|
|
105
|
+
* {@link MAX_DOMAIN_LENGTH} or under {@link MIN_DOMAIN_LENGTH}), `public` (a public mail
|
|
106
|
+
* domain), or `null`.
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* domainRefusal('acme.com') // null
|
|
110
|
+
* domainRefusal('gmail.com') // 'public'
|
|
111
|
+
* domainRefusal('bücher.example') // 'grammar'
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
declare function domainRefusal(domain: string): DomainRefusal | null;
|
|
115
|
+
/**
|
|
116
|
+
* The domain of an email address in the spelling the server stores (lower case), or `null`
|
|
117
|
+
* when the text has no `@` or ends in one.
|
|
118
|
+
*
|
|
119
|
+
* It is everything after the **last** `@`, unchanged: no subdomain is cut off, so
|
|
120
|
+
* `a@mail.acme.com` is `mail.acme.com` and **never** matches a claim on `acme.com`. Whether the
|
|
121
|
+
* result is a domain at all is {@link domainRefusal}'s question.
|
|
122
|
+
*
|
|
123
|
+
* @param address - An email address, normalised (lower case) by the caller.
|
|
124
|
+
* @returns The part after the last `@`, or `null`.
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* domainOfAddress('ada@acme.com') // 'acme.com'
|
|
128
|
+
* domainOfAddress('ada@mail.acme.com') // 'mail.acme.com'
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function domainOfAddress(address: string): string | null;
|
|
132
|
+
export { DOMAIN_PATTERN, DOMAIN_REMINDER_DAYS, DOMAIN_REVERIFICATION_GRACE_DAYS, DOMAIN_VERIFICATION_LIFETIME_DAYS, DomainRefusal, MAX_DOMAINS_PER_ORGANIZATION, MAX_DOMAIN_LABEL_LENGTH, MAX_DOMAIN_LENGTH, MIN_DOMAIN_LENGTH, ORGANIZATION_DOMAIN_MODES, ORGANIZATION_DOMAIN_STATUSES, OrganizationDomainMode, OrganizationDomainStatus, PUBLIC_EMAIL_BRANDS, PUBLIC_EMAIL_DOMAINS, domainOfAddress, domainRefusal, isPublicEmailDomain };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_DOMAIN_LENGTH2,
|
|
3
|
+
MAX_DOMAIN_LABEL_LENGTH2,
|
|
4
|
+
MIN_DOMAIN_LENGTH2,
|
|
5
|
+
MAX_DOMAINS_PER_ORGANIZATION2,
|
|
6
|
+
DOMAIN_VERIFICATION_LIFETIME_DAYS2,
|
|
7
|
+
DOMAIN_REMINDER_DAYS2,
|
|
8
|
+
DOMAIN_REVERIFICATION_GRACE_DAYS2,
|
|
9
|
+
ORGANIZATION_DOMAIN_STATUSES2,
|
|
10
|
+
ORGANIZATION_DOMAIN_MODES2,
|
|
11
|
+
DOMAIN_PATTERN2,
|
|
12
|
+
PUBLIC_EMAIL_DOMAINS2,
|
|
13
|
+
PUBLIC_EMAIL_BRANDS2,
|
|
14
|
+
isPublicEmailDomain2,
|
|
15
|
+
domainRefusal2,
|
|
16
|
+
domainOfAddress2
|
|
17
|
+
} from "./shared/chunk-cdg41bxh.js";
|
|
18
|
+
export {
|
|
19
|
+
DOMAIN_PATTERN2 as DOMAIN_PATTERN,
|
|
20
|
+
DOMAIN_REMINDER_DAYS2 as DOMAIN_REMINDER_DAYS,
|
|
21
|
+
DOMAIN_REVERIFICATION_GRACE_DAYS2 as DOMAIN_REVERIFICATION_GRACE_DAYS,
|
|
22
|
+
DOMAIN_VERIFICATION_LIFETIME_DAYS2 as DOMAIN_VERIFICATION_LIFETIME_DAYS,
|
|
23
|
+
MAX_DOMAINS_PER_ORGANIZATION2 as MAX_DOMAINS_PER_ORGANIZATION,
|
|
24
|
+
MAX_DOMAIN_LABEL_LENGTH2 as MAX_DOMAIN_LABEL_LENGTH,
|
|
25
|
+
MAX_DOMAIN_LENGTH2 as MAX_DOMAIN_LENGTH,
|
|
26
|
+
MIN_DOMAIN_LENGTH2 as MIN_DOMAIN_LENGTH,
|
|
27
|
+
ORGANIZATION_DOMAIN_MODES2 as ORGANIZATION_DOMAIN_MODES,
|
|
28
|
+
ORGANIZATION_DOMAIN_STATUSES2 as ORGANIZATION_DOMAIN_STATUSES,
|
|
29
|
+
PUBLIC_EMAIL_BRANDS2 as PUBLIC_EMAIL_BRANDS,
|
|
30
|
+
PUBLIC_EMAIL_DOMAINS2 as PUBLIC_EMAIL_DOMAINS,
|
|
31
|
+
domainOfAddress2 as domainOfAddress,
|
|
32
|
+
domainRefusal2 as domainRefusal,
|
|
33
|
+
isPublicEmailDomain2 as isPublicEmailDomain
|
|
34
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many permissions one role can hold. The permissions of the active organization's role are
|
|
3
|
+
* a claim of every access token, so the number is capped.
|
|
4
|
+
*/
|
|
5
|
+
declare const MAX_PERMISSIONS_PER_ROLE = 32;
|
|
6
|
+
/** How many custom roles an environment can define, beside the built-in ones. */
|
|
7
|
+
declare const MAX_CUSTOM_ROLES = 50;
|
|
8
|
+
/** The longest a permission key can be, in characters. */
|
|
9
|
+
declare const MAX_PERMISSION_KEY_LENGTH = 100;
|
|
10
|
+
/**
|
|
11
|
+
* The grammar of a role's key: a lower-case letter, then lower-case letters, digits and
|
|
12
|
+
* underscores, up to 32 characters (`admin`, `billing_manager`). Unique in its environment.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* ROLE_KEY_PATTERN.test('billing_manager') // true
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare const ROLE_KEY_PATTERN: RegExp;
|
|
20
|
+
/**
|
|
21
|
+
* The grammar of a permission key: `org:<feature>:<action>`, each of the last two
|
|
22
|
+
* a lower-case letter followed by lower-case letters, digits and underscores (at most 32
|
|
23
|
+
* characters); or, for the permissions Tula's own routes check, `org:sys:<feature>:<action>`.
|
|
24
|
+
* No dot, no other separator: a key is safe in a token and in a URL. The feature `sys` is
|
|
25
|
+
* reserved: a custom key never has it, and a system key is one of
|
|
26
|
+
* {@link SYSTEM_PERMISSIONS}.
|
|
27
|
+
*
|
|
28
|
+
* The pattern is the grammar alone. Whether a role may hold a key is
|
|
29
|
+
* {@link rolePermissionRefusal}.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* PERMISSION_KEY_PATTERN.test('org:billing:read') // true
|
|
34
|
+
* PERMISSION_KEY_PATTERN.test('org:sys:memberships:manage') // true
|
|
35
|
+
* PERMISSION_KEY_PATTERN.test('billing.read') // false
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const PERMISSION_KEY_PATTERN: RegExp;
|
|
39
|
+
/**
|
|
40
|
+
* The permissions Tula's own organization routes check. The same keys an app's
|
|
41
|
+
* server checks with `has({ permission })`, so Tula's routes hold no privilege an app cannot
|
|
42
|
+
* also give a role.
|
|
43
|
+
*/
|
|
44
|
+
declare const SYSTEM_PERMISSIONS: readonly ["org:sys:organization:manage", "org:sys:organization:delete", "org:sys:memberships:read", "org:sys:memberships:manage", "org:sys:invitations:manage", "org:sys:domains:manage", "org:sys:sso:manage"];
|
|
45
|
+
/** One of {@link SYSTEM_PERMISSIONS}. */
|
|
46
|
+
type SystemPermission = (typeof SYSTEM_PERMISSIONS)[number];
|
|
47
|
+
/**
|
|
48
|
+
* The permission that makes a member a manager of the organization's members. **Every
|
|
49
|
+
* organization keeps at least one member who holds it**: the last such member cannot be
|
|
50
|
+
* removed or moved to a role without it (`organization.last_manager`), and a role's
|
|
51
|
+
* permissions cannot be changed so that an organization would be left with none.
|
|
52
|
+
*/
|
|
53
|
+
declare const MEMBERSHIPS_MANAGE_PERMISSION: SystemPermission;
|
|
54
|
+
/**
|
|
55
|
+
* The permission that lets a member claim, verify, change and remove the organization's
|
|
56
|
+
* domains through the client routes. Read from the member's
|
|
57
|
+
* role at the time of the request, never from a token.
|
|
58
|
+
*/
|
|
59
|
+
declare const DOMAINS_MANAGE_PERMISSION: SystemPermission;
|
|
60
|
+
/**
|
|
61
|
+
* The permission that lets a member invite people to the organization and withdraw
|
|
62
|
+
* invitations. Checked on the server from the membership as it is now,
|
|
63
|
+
* never from a token's claims alone.
|
|
64
|
+
*/
|
|
65
|
+
declare const INVITATIONS_MANAGE_PERMISSION: SystemPermission;
|
|
66
|
+
/**
|
|
67
|
+
* The permission that lets a member rename the organization through the client routes.
|
|
68
|
+
* Read from the membership now, never from a token's claims alone.
|
|
69
|
+
*/
|
|
70
|
+
declare const ORGANIZATION_MANAGE_PERMISSION: SystemPermission;
|
|
71
|
+
/**
|
|
72
|
+
* The permission that lets a member delete the organization through the client routes.
|
|
73
|
+
* Read from the membership now, never from a token's claims alone.
|
|
74
|
+
*/
|
|
75
|
+
declare const ORGANIZATION_DELETE_PERMISSION: SystemPermission;
|
|
76
|
+
/**
|
|
77
|
+
* The permission that lets a member read the organization's member list through the client
|
|
78
|
+
* routes. `org:sys:memberships:manage` also reads it.
|
|
79
|
+
*/
|
|
80
|
+
declare const MEMBERSHIPS_READ_PERMISSION: SystemPermission;
|
|
81
|
+
/**
|
|
82
|
+
* The permission that lets a member manage the organization's single sign-on connections.
|
|
83
|
+
* Read from the membership now, never from a token's claims alone.
|
|
84
|
+
*/
|
|
85
|
+
declare const SSO_MANAGE_PERMISSION: SystemPermission;
|
|
86
|
+
/** A built-in role: its key, its name and the permissions it holds. */
|
|
87
|
+
interface BuiltInRole {
|
|
88
|
+
readonly key: string;
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly permissions: readonly SystemPermission[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The two roles every environment has: `admin` holds every system permission,
|
|
94
|
+
* `member` may read the member list. They cannot be deleted, renamed or given other
|
|
95
|
+
* permissions. They exist as rows of every environment (`ensureBuiltInRoles`) so that a
|
|
96
|
+
* membership refers to a role the way it does to a custom one.
|
|
97
|
+
*/
|
|
98
|
+
declare const BUILT_IN_ROLES: readonly BuiltInRole[];
|
|
99
|
+
/**
|
|
100
|
+
* Whether a string is one of {@link SYSTEM_PERMISSIONS}.
|
|
101
|
+
*
|
|
102
|
+
* @param key - A permission key.
|
|
103
|
+
* @returns `true` for one of Tula's own permissions.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* isSystemPermission('org:sys:memberships:read') // true
|
|
108
|
+
* isSystemPermission('org:billing:read') // false
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare function isSystemPermission(key: string): key is SystemPermission;
|
|
112
|
+
/**
|
|
113
|
+
* Whether a string is the key of a built-in role (`admin`, `member`). A custom role never
|
|
114
|
+
* takes one of these keys.
|
|
115
|
+
*
|
|
116
|
+
* @param key - A role key.
|
|
117
|
+
* @returns `true` for a built-in role's key.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* isBuiltInRoleKey('admin') // true
|
|
122
|
+
* isBuiltInRoleKey('billing_manager') // false
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare function isBuiltInRoleKey(key: string): boolean;
|
|
126
|
+
/** Why a custom role may not hold a permission key. */
|
|
127
|
+
type RolePermissionRefusal = "grammar" | "reserved";
|
|
128
|
+
/**
|
|
129
|
+
* Whether a **custom** role may hold a permission key.
|
|
130
|
+
*
|
|
131
|
+
* A custom role holds the operator's own permissions (`org:billing:read`) and may also hold
|
|
132
|
+
* any of Tula's {@link SYSTEM_PERMISSIONS}, so that a role such as `owner` can manage members.
|
|
133
|
+
* It can never hold a key of the reserved `sys` feature that is not on that list: a key Tula
|
|
134
|
+
* does not check would mean nothing today and silently grant a privilege tomorrow, when a
|
|
135
|
+
* later version gives that key a meaning.
|
|
136
|
+
*
|
|
137
|
+
* @param key - A permission key.
|
|
138
|
+
* @returns `null` when the role may hold it, `'grammar'` when it is not a permission key at
|
|
139
|
+
* all, `'reserved'` when it is a `sys` key Tula does not define.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* rolePermissionRefusal('org:billing:read') // null
|
|
144
|
+
* rolePermissionRefusal('org:sys:memberships:manage') // null
|
|
145
|
+
* rolePermissionRefusal('org:sys:billing:read') // 'reserved'
|
|
146
|
+
* rolePermissionRefusal('billing.read') // 'grammar'
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function rolePermissionRefusal(key: string): RolePermissionRefusal | null;
|
|
150
|
+
/**
|
|
151
|
+
* The permissions of a role as a set: each key once, sorted. How a role is stored, compared
|
|
152
|
+
* and returned, so that the order a caller listed them in is never a difference.
|
|
153
|
+
*
|
|
154
|
+
* @param permissions - Permission keys, in any order, possibly with repeats.
|
|
155
|
+
* @returns The distinct keys, sorted.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* normalizePermissions(['org:b:read', 'org:a:read', 'org:b:read']) // ['org:a:read', 'org:b:read']
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
declare function normalizePermissions(permissions: readonly string[]): string[];
|
|
163
|
+
/**
|
|
164
|
+
* What a change to a custom role's permissions widens, the one rule the server records
|
|
165
|
+
* `weakened` with, `tula apply` asks `--allow-weaker` with and the dashboard asks first with.
|
|
166
|
+
*
|
|
167
|
+
* **Adding a permission to a role is a weakening** (members who hold the role gain access at
|
|
168
|
+
* their next refresh); taking one away is not. **Creating a role is not one**: no member holds
|
|
169
|
+
* it until someone assigns it, and that assignment is a membership change of its own. Deleting
|
|
170
|
+
* a role is not one: only a role no member holds can be deleted.
|
|
171
|
+
*
|
|
172
|
+
* @param before - The permissions the role has, or `null` for a role that does not exist yet.
|
|
173
|
+
* @param after - The permissions it would have.
|
|
174
|
+
* @returns `['permissions']` when `after` holds a key `before` does not, otherwise `[]`.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* organizationRoleWeakenings(['org:billing:read'], ['org:billing:read', 'org:billing:write'])
|
|
179
|
+
* // ['permissions']
|
|
180
|
+
* organizationRoleWeakenings(['org:billing:read'], []) // []
|
|
181
|
+
* organizationRoleWeakenings(null, ['org:billing:read']) // []
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function organizationRoleWeakenings(before: readonly string[] | null, after: readonly string[]): "permissions"[];
|
|
185
|
+
export { BUILT_IN_ROLES, BuiltInRole, DOMAINS_MANAGE_PERMISSION, INVITATIONS_MANAGE_PERMISSION, MAX_CUSTOM_ROLES, MAX_PERMISSIONS_PER_ROLE, MAX_PERMISSION_KEY_LENGTH, MEMBERSHIPS_MANAGE_PERMISSION, MEMBERSHIPS_READ_PERMISSION, ORGANIZATION_DELETE_PERMISSION, ORGANIZATION_MANAGE_PERMISSION, PERMISSION_KEY_PATTERN, ROLE_KEY_PATTERN, RolePermissionRefusal, SSO_MANAGE_PERMISSION, SYSTEM_PERMISSIONS, SystemPermission, isBuiltInRoleKey, isSystemPermission, normalizePermissions, organizationRoleWeakenings, rolePermissionRefusal };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_PERMISSIONS_PER_ROLE2,
|
|
3
|
+
MAX_CUSTOM_ROLES2,
|
|
4
|
+
MAX_PERMISSION_KEY_LENGTH2,
|
|
5
|
+
ROLE_KEY_PATTERN2,
|
|
6
|
+
PERMISSION_KEY_PATTERN2,
|
|
7
|
+
SYSTEM_PERMISSIONS2,
|
|
8
|
+
MEMBERSHIPS_MANAGE_PERMISSION2,
|
|
9
|
+
DOMAINS_MANAGE_PERMISSION2,
|
|
10
|
+
INVITATIONS_MANAGE_PERMISSION2,
|
|
11
|
+
ORGANIZATION_MANAGE_PERMISSION2,
|
|
12
|
+
ORGANIZATION_DELETE_PERMISSION2,
|
|
13
|
+
MEMBERSHIPS_READ_PERMISSION2,
|
|
14
|
+
SSO_MANAGE_PERMISSION2,
|
|
15
|
+
BUILT_IN_ROLES2,
|
|
16
|
+
isSystemPermission2,
|
|
17
|
+
isBuiltInRoleKey2,
|
|
18
|
+
rolePermissionRefusal2,
|
|
19
|
+
normalizePermissions2,
|
|
20
|
+
organizationRoleWeakenings2
|
|
21
|
+
} from "./shared/chunk-yzgakhtb.js";
|
|
22
|
+
export {
|
|
23
|
+
BUILT_IN_ROLES2 as BUILT_IN_ROLES,
|
|
24
|
+
DOMAINS_MANAGE_PERMISSION2 as DOMAINS_MANAGE_PERMISSION,
|
|
25
|
+
INVITATIONS_MANAGE_PERMISSION2 as INVITATIONS_MANAGE_PERMISSION,
|
|
26
|
+
MAX_CUSTOM_ROLES2 as MAX_CUSTOM_ROLES,
|
|
27
|
+
MAX_PERMISSIONS_PER_ROLE2 as MAX_PERMISSIONS_PER_ROLE,
|
|
28
|
+
MAX_PERMISSION_KEY_LENGTH2 as MAX_PERMISSION_KEY_LENGTH,
|
|
29
|
+
MEMBERSHIPS_MANAGE_PERMISSION2 as MEMBERSHIPS_MANAGE_PERMISSION,
|
|
30
|
+
MEMBERSHIPS_READ_PERMISSION2 as MEMBERSHIPS_READ_PERMISSION,
|
|
31
|
+
ORGANIZATION_DELETE_PERMISSION2 as ORGANIZATION_DELETE_PERMISSION,
|
|
32
|
+
ORGANIZATION_MANAGE_PERMISSION2 as ORGANIZATION_MANAGE_PERMISSION,
|
|
33
|
+
PERMISSION_KEY_PATTERN2 as PERMISSION_KEY_PATTERN,
|
|
34
|
+
ROLE_KEY_PATTERN2 as ROLE_KEY_PATTERN,
|
|
35
|
+
SSO_MANAGE_PERMISSION2 as SSO_MANAGE_PERMISSION,
|
|
36
|
+
SYSTEM_PERMISSIONS2 as SYSTEM_PERMISSIONS,
|
|
37
|
+
isBuiltInRoleKey2 as isBuiltInRoleKey,
|
|
38
|
+
isSystemPermission2 as isSystemPermission,
|
|
39
|
+
normalizePermissions2 as normalizePermissions,
|
|
40
|
+
organizationRoleWeakenings2 as organizationRoleWeakenings,
|
|
41
|
+
rolePermissionRefusal2 as rolePermissionRefusal
|
|
42
|
+
};
|
package/dist/password-rules.d.ts
CHANGED
|
@@ -108,6 +108,22 @@ declare const ERROR_DEFINITIONS: {
|
|
|
108
108
|
readonly status: 409;
|
|
109
109
|
readonly message: "This account has no password yet. Use \"forgot password\" to set one.";
|
|
110
110
|
};
|
|
111
|
+
readonly "import.unsupported_algorithm": {
|
|
112
|
+
readonly status: 422;
|
|
113
|
+
readonly message: "That password hash algorithm is not supported.";
|
|
114
|
+
};
|
|
115
|
+
readonly "import.hash_malformed": {
|
|
116
|
+
readonly status: 422;
|
|
117
|
+
readonly message: "That password hash is malformed.";
|
|
118
|
+
};
|
|
119
|
+
readonly "import.hash_parameters_too_weak": {
|
|
120
|
+
readonly status: 422;
|
|
121
|
+
readonly message: "That password hash is too weak to import.";
|
|
122
|
+
};
|
|
123
|
+
readonly "import.hash_parameters_too_costly": {
|
|
124
|
+
readonly status: 422;
|
|
125
|
+
readonly message: "That password hash is too costly to verify.";
|
|
126
|
+
};
|
|
111
127
|
readonly "verification.invalid_code": {
|
|
112
128
|
readonly status: 422;
|
|
113
129
|
readonly message: "That code is incorrect.";
|
|
@@ -300,6 +316,194 @@ declare const ERROR_DEFINITIONS: {
|
|
|
300
316
|
readonly status: 400;
|
|
301
317
|
readonly message: "Signing in here needs a device key.";
|
|
302
318
|
};
|
|
319
|
+
readonly "user.last_organization_manager": {
|
|
320
|
+
readonly status: 409;
|
|
321
|
+
readonly message: "This user is the only manager of an organization. Add another manager or delete the organization first.";
|
|
322
|
+
};
|
|
323
|
+
readonly "user.managed": {
|
|
324
|
+
readonly status: 403;
|
|
325
|
+
readonly message: "This account is managed by your organization. Ask your administrator to change it.";
|
|
326
|
+
};
|
|
327
|
+
readonly "organization.disabled": {
|
|
328
|
+
readonly status: 403;
|
|
329
|
+
readonly message: "Organizations are not enabled for this environment.";
|
|
330
|
+
};
|
|
331
|
+
readonly "organization.slug_taken": {
|
|
332
|
+
readonly status: 409;
|
|
333
|
+
readonly message: "An organization with that slug already exists.";
|
|
334
|
+
};
|
|
335
|
+
readonly "organization.member_exists": {
|
|
336
|
+
readonly status: 409;
|
|
337
|
+
readonly message: "That user is already a member of this organization.";
|
|
338
|
+
};
|
|
339
|
+
readonly "organization.last_manager": {
|
|
340
|
+
readonly status: 409;
|
|
341
|
+
readonly message: "An organization must keep at least one member who can manage its members.";
|
|
342
|
+
};
|
|
343
|
+
readonly "organization.role_unknown": {
|
|
344
|
+
readonly status: 422;
|
|
345
|
+
readonly message: "That role does not exist in this environment.";
|
|
346
|
+
};
|
|
347
|
+
readonly "organization.not_a_member": {
|
|
348
|
+
readonly status: 403;
|
|
349
|
+
readonly message: "You are not a member of that organization.";
|
|
350
|
+
};
|
|
351
|
+
readonly "organization.creation_disabled": {
|
|
352
|
+
readonly status: 403;
|
|
353
|
+
readonly message: "Creating an organization is not allowed in this environment.";
|
|
354
|
+
};
|
|
355
|
+
readonly "organization.limit_reached": {
|
|
356
|
+
readonly status: 403;
|
|
357
|
+
readonly message: "You have reached the number of organizations you can create.";
|
|
358
|
+
};
|
|
359
|
+
readonly "organization.personal_account_disabled": {
|
|
360
|
+
readonly status: 409;
|
|
361
|
+
readonly message: "An organization must be active in this environment.";
|
|
362
|
+
};
|
|
363
|
+
readonly "organization.role_exists": {
|
|
364
|
+
readonly status: 409;
|
|
365
|
+
readonly message: "A role with that key already exists in this environment.";
|
|
366
|
+
};
|
|
367
|
+
readonly "organization.role_in_use": {
|
|
368
|
+
readonly status: 409;
|
|
369
|
+
readonly message: "Members still hold this role. Give them another role first.";
|
|
370
|
+
};
|
|
371
|
+
readonly "organization.role_built_in": {
|
|
372
|
+
readonly status: 409;
|
|
373
|
+
readonly message: "A built-in role cannot be changed or deleted.";
|
|
374
|
+
};
|
|
375
|
+
readonly "organization.role_limit": {
|
|
376
|
+
readonly status: 409;
|
|
377
|
+
readonly message: "This environment has the most custom roles it can have.";
|
|
378
|
+
};
|
|
379
|
+
readonly "organization.permission_required": {
|
|
380
|
+
readonly status: 403;
|
|
381
|
+
readonly message: "Your role does not allow that.";
|
|
382
|
+
};
|
|
383
|
+
readonly "organization.role_not_assignable": {
|
|
384
|
+
readonly status: 403;
|
|
385
|
+
readonly message: "You can only invite to a role no stronger than yours.";
|
|
386
|
+
};
|
|
387
|
+
readonly "organization.invitation_exists": {
|
|
388
|
+
readonly status: 409;
|
|
389
|
+
readonly message: "That address already has a pending invitation.";
|
|
390
|
+
};
|
|
391
|
+
readonly "organization.invitation_not_found": {
|
|
392
|
+
readonly status: 404;
|
|
393
|
+
readonly message: "This invitation does not exist.";
|
|
394
|
+
};
|
|
395
|
+
readonly "organization.invitation_expired": {
|
|
396
|
+
readonly status: 410;
|
|
397
|
+
readonly message: "This invitation has expired.";
|
|
398
|
+
};
|
|
399
|
+
readonly "organization.invitation_revoked": {
|
|
400
|
+
readonly status: 410;
|
|
401
|
+
readonly message: "This invitation was withdrawn.";
|
|
402
|
+
};
|
|
403
|
+
readonly "organization.invitation_used": {
|
|
404
|
+
readonly status: 410;
|
|
405
|
+
readonly message: "This invitation was already used.";
|
|
406
|
+
};
|
|
407
|
+
readonly "organization.invitation_wrong_account": {
|
|
408
|
+
readonly status: 403;
|
|
409
|
+
readonly message: "This invitation was sent to another email address.";
|
|
410
|
+
};
|
|
411
|
+
readonly "organization.invitation_email_unverified": {
|
|
412
|
+
readonly status: 403;
|
|
413
|
+
readonly message: "Verify your email address to accept this invitation.";
|
|
414
|
+
};
|
|
415
|
+
readonly "organization.invitation_role_gone": {
|
|
416
|
+
readonly status: 409;
|
|
417
|
+
readonly message: "The role this invitation offers no longer exists.";
|
|
418
|
+
};
|
|
419
|
+
readonly "organization.domain_public": {
|
|
420
|
+
readonly status: 422;
|
|
421
|
+
readonly message: "A public email domain cannot be claimed by an organization.";
|
|
422
|
+
};
|
|
423
|
+
readonly "organization.domain_taken": {
|
|
424
|
+
readonly status: 409;
|
|
425
|
+
readonly message: "That domain is verified for an organization already.";
|
|
426
|
+
};
|
|
427
|
+
readonly "organization.domain_address_mismatch": {
|
|
428
|
+
readonly status: 422;
|
|
429
|
+
readonly message: "That address is not on the domain.";
|
|
430
|
+
};
|
|
431
|
+
readonly "organization.domain_limit": {
|
|
432
|
+
readonly status: 409;
|
|
433
|
+
readonly message: "This organization has the most domains it can have.";
|
|
434
|
+
};
|
|
435
|
+
readonly "organization.suggestion_unavailable": {
|
|
436
|
+
readonly status: 403;
|
|
437
|
+
readonly message: "You cannot join that organization.";
|
|
438
|
+
};
|
|
439
|
+
readonly "sso.disabled": {
|
|
440
|
+
readonly status: 403;
|
|
441
|
+
readonly message: "Single sign-on is not enabled for this environment.";
|
|
442
|
+
};
|
|
443
|
+
readonly "sso.connection_not_found": {
|
|
444
|
+
readonly status: 404;
|
|
445
|
+
readonly message: "No such single sign-on connection.";
|
|
446
|
+
};
|
|
447
|
+
readonly "sso.name_taken": {
|
|
448
|
+
readonly status: 409;
|
|
449
|
+
readonly message: "The organization already has a connection with that name.";
|
|
450
|
+
};
|
|
451
|
+
readonly "sso.domain_taken": {
|
|
452
|
+
readonly status: 409;
|
|
453
|
+
readonly message: "Another active connection already serves one of those domains.";
|
|
454
|
+
};
|
|
455
|
+
readonly "sso.domain_not_verified": {
|
|
456
|
+
readonly status: 422;
|
|
457
|
+
readonly message: "Every domain must be one of the organization's verified domains.";
|
|
458
|
+
};
|
|
459
|
+
readonly "sso.issuer_not_allowed": {
|
|
460
|
+
readonly status: 422;
|
|
461
|
+
readonly message: "The issuer is not an address the server may call.";
|
|
462
|
+
};
|
|
463
|
+
readonly "sso.saml_invalid": {
|
|
464
|
+
readonly status: 422;
|
|
465
|
+
readonly message: "The SAML settings could not be read or are incomplete.";
|
|
466
|
+
};
|
|
467
|
+
readonly "sso.connection_in_use": {
|
|
468
|
+
readonly status: 409;
|
|
469
|
+
readonly message: "The connection is active; disable it before deleting it.";
|
|
470
|
+
};
|
|
471
|
+
readonly "sso.invalid_transition": {
|
|
472
|
+
readonly status: 409;
|
|
473
|
+
readonly message: "A connection cannot move to that status from its current one.";
|
|
474
|
+
};
|
|
475
|
+
readonly "sso.url_not_https": {
|
|
476
|
+
readonly status: 422;
|
|
477
|
+
readonly message: "The identity provider’s sign-in address must start with https.";
|
|
478
|
+
};
|
|
479
|
+
readonly "sso.test_required": {
|
|
480
|
+
readonly status: 409;
|
|
481
|
+
readonly message: "Run a passing test sign-in of the connection’s current settings before activating it.";
|
|
482
|
+
};
|
|
483
|
+
readonly "sso.test_not_found": {
|
|
484
|
+
readonly status: 404;
|
|
485
|
+
readonly message: "No such test sign-in.";
|
|
486
|
+
};
|
|
487
|
+
readonly "sso.role_not_allowed": {
|
|
488
|
+
readonly status: 403;
|
|
489
|
+
readonly message: "A setup link cannot map a group to a role that holds system permissions.";
|
|
490
|
+
};
|
|
491
|
+
readonly "sso.unavailable": {
|
|
492
|
+
readonly status: 400;
|
|
493
|
+
readonly message: "Single sign-on is not available for that address.";
|
|
494
|
+
};
|
|
495
|
+
readonly "sso.provider_error": {
|
|
496
|
+
readonly status: 401;
|
|
497
|
+
readonly message: "The identity provider did not sign you in.";
|
|
498
|
+
};
|
|
499
|
+
readonly "sso.user_not_provisioned": {
|
|
500
|
+
readonly status: 403;
|
|
501
|
+
readonly message: "Your account has not been set up for this organization. Ask its administrator.";
|
|
502
|
+
};
|
|
503
|
+
readonly "sso.email_domain_mismatch": {
|
|
504
|
+
readonly status: 403;
|
|
505
|
+
readonly message: "Your email address is not on this connection's domains.";
|
|
506
|
+
};
|
|
303
507
|
readonly rate_limited: {
|
|
304
508
|
readonly status: 429;
|
|
305
509
|
readonly message: "Too many requests. Try again shortly.";
|
package/dist/redirect-url.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* {@link REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM}, are refused by this list alone, and a
|
|
10
10
|
* scheme an operating system handles that nobody put here passes. What bounds that is not
|
|
11
11
|
* the list: every entry of `urls.allowedRedirectUrls` is written by the operator by hand,
|
|
12
|
-
* and a redirect carries only a ticket that is useless without the binding
|
|
12
|
+
* and a redirect carries only a ticket that is useless without the binding.
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```ts
|
|
@@ -134,7 +134,7 @@ declare function isRedirectUrl(value: string): boolean;
|
|
|
134
134
|
*/
|
|
135
135
|
declare function isCustomSchemeRedirectUrl(value: string): boolean;
|
|
136
136
|
/**
|
|
137
|
-
* The providers whose authorization code Tula binds to the attempt with PKCE
|
|
137
|
+
* The providers whose authorization code Tula binds to the attempt with PKCE: the
|
|
138
138
|
* code is redeemable only with the verifier the attempt holds.
|
|
139
139
|
*
|
|
140
140
|
* @example
|
|
@@ -146,7 +146,7 @@ declare const OAUTH_PROVIDERS_WITH_PKCE: readonly ["google", "github", "microsof
|
|
|
146
146
|
/**
|
|
147
147
|
* The providers that do **not** bind their code with PKCE, as a fact about the provider and
|
|
148
148
|
* not a setting: Apple and LinkedIn document none, and Facebook's has it only in a flow that
|
|
149
|
-
* is not used
|
|
149
|
+
* is not used.
|
|
150
150
|
*
|
|
151
151
|
* A provider sign-in with one of them is never returned to a custom scheme
|
|
152
152
|
* ({@link customSchemeRedirectRefusal}). A new provider is added to this list or to
|
package/dist/redirect-url.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
bindsCodeWithPkce2,
|
|
11
11
|
CUSTOM_SCHEME_REDIRECT_REFUSALS2,
|
|
12
12
|
customSchemeRedirectRefusal2
|
|
13
|
-
} from "./shared/chunk-
|
|
13
|
+
} from "./shared/chunk-n1jdq72m.js";
|
|
14
14
|
export {
|
|
15
15
|
CUSTOM_SCHEME_REDIRECT_REFUSALS2 as CUSTOM_SCHEME_REDIRECT_REFUSALS,
|
|
16
16
|
OAUTH_PROVIDERS_WITHOUT_PKCE2 as OAUTH_PROVIDERS_WITHOUT_PKCE,
|
|
@@ -12,8 +12,10 @@ var DASHBOARD_HEADER_VALUE2 = "1";
|
|
|
12
12
|
var ENVIRONMENT_HEADER2 = "x-tula-environment";
|
|
13
13
|
var DASHBOARD_SESSION_COOKIE2 = "tula_dashboard";
|
|
14
14
|
var EMAIL_LINK_TOKEN_PARAM2 = "tula_link";
|
|
15
|
+
var ORGANIZATION_INVITATION_TOKEN_PARAM2 = "tula_invitation";
|
|
15
16
|
var EMAIL_LINK_ATTEMPT_PARAM2 = "tula_attempt";
|
|
16
17
|
var OAUTH_TICKET_PARAM2 = "tula_ticket";
|
|
17
18
|
var OAUTH_ERROR_PARAM2 = "tula_error";
|
|
19
|
+
var SETUP_LINK_HEADER2 = "x-tula-setup-token";
|
|
18
20
|
|
|
19
|
-
export { PUBLISHABLE_KEY_HEADER2, CLIENT_HEADER2, SESSION_PROFILE_HEADER2, FLOW_ATTEMPT_HEADER2, CAN_STILL_SIGN_IN_HEADER2, CONFIG_MANAGED_BY_HEADER2, CONFIG_HASH_HEADER2, CONFIG_UNMANAGED2, DASHBOARD_HEADER2, DASHBOARD_HEADER_VALUE2, ENVIRONMENT_HEADER2, DASHBOARD_SESSION_COOKIE2, EMAIL_LINK_TOKEN_PARAM2, EMAIL_LINK_ATTEMPT_PARAM2, OAUTH_TICKET_PARAM2, OAUTH_ERROR_PARAM2 };
|
|
21
|
+
export { PUBLISHABLE_KEY_HEADER2, CLIENT_HEADER2, SESSION_PROFILE_HEADER2, FLOW_ATTEMPT_HEADER2, CAN_STILL_SIGN_IN_HEADER2, CONFIG_MANAGED_BY_HEADER2, CONFIG_HASH_HEADER2, CONFIG_UNMANAGED2, DASHBOARD_HEADER2, DASHBOARD_HEADER_VALUE2, ENVIRONMENT_HEADER2, DASHBOARD_SESSION_COOKIE2, EMAIL_LINK_TOKEN_PARAM2, ORGANIZATION_INVITATION_TOKEN_PARAM2, EMAIL_LINK_ATTEMPT_PARAM2, OAUTH_TICKET_PARAM2, OAUTH_ERROR_PARAM2, SETUP_LINK_HEADER2 };
|