@udibo/oauth2 0.6.0 → 0.8.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.
@@ -27,10 +27,11 @@
27
27
  * prototypes — never for production.
28
28
  *
29
29
  * - **A fake Udibo Identity tenant.** {@link createFakeTenant} answers a tenant's
30
- * protocol surface and its `/api/check` questions over `fetch`, with the
31
- * claims a real tenant issues — permissions, the organization picked at
32
- * sign-in, and resource grants — so an app built on a Udibo Identity tenant can test
33
- * its own routes without running the identity service.
30
+ * protocol surface, its `/api/check` questions, and its organization and
31
+ * account APIs over `fetch`, with the claims a real tenant issues —
32
+ * permissions, the organization picked at sign-in, and resource grants — so
33
+ * an app built on a Udibo Identity tenant can test its own routes without
34
+ * running the identity service.
34
35
  *
35
36
  * Nothing in this module is an OAuth2 primitive — only testing support.
36
37
  * Production code should never import from here.
@@ -40,4 +41,4 @@
40
41
  import "../_dnt.polyfills.js";
41
42
  export { MemoryAuthorizationCodeService, type MemoryAuthorizationCodeServiceOptions, MemoryClientService, MemoryDeviceAuthorizationService, type MemoryDeviceAuthorizationServiceOptions, MemoryTokenService, type MemoryTokenServiceOptions, MemoryUserService, type MemoryUserServiceOptions, type MemoryUserShape, } from "./services.js";
42
43
  export { createMemoryAuthorizationServer, type CreateMemoryAuthorizationServerOptions, type MemoryAuthorizationServerClientSeed, type MemoryAuthorizationServerGrants, type MemoryAuthorizationServerHarness, type MemoryAuthorizationServerUserSeed, } from "./server.js";
43
- export { createFakeTenant, type FakeTenant, type FakeTenantClient, type FakeTenantGrant, type FakeTenantMembership, type FakeTenantOptions, type FakeTenantOrganization, type FakeTenantSignIn, type FakeTenantSubject, type FakeTenantTokenRequest, type FakeTenantUser, } from "./tenant.js";
44
+ export { createFakeTenant, type FakeTenant, type FakeTenantClient, type FakeTenantGrant, type FakeTenantLinkedAccount, type FakeTenantMembership, type FakeTenantOptions, type FakeTenantOrganization, type FakeTenantOrganizationRole, type FakeTenantSignIn, type FakeTenantSubject, type FakeTenantTokenRequest, type FakeTenantUser, } from "./tenant.js";
@@ -27,10 +27,11 @@
27
27
  * prototypes — never for production.
28
28
  *
29
29
  * - **A fake Udibo Identity tenant.** {@link createFakeTenant} answers a tenant's
30
- * protocol surface and its `/api/check` questions over `fetch`, with the
31
- * claims a real tenant issues — permissions, the organization picked at
32
- * sign-in, and resource grants — so an app built on a Udibo Identity tenant can test
33
- * its own routes without running the identity service.
30
+ * protocol surface, its `/api/check` questions, and its organization and
31
+ * account APIs over `fetch`, with the claims a real tenant issues —
32
+ * permissions, the organization picked at sign-in, and resource grants — so
33
+ * an app built on a Udibo Identity tenant can test its own routes without
34
+ * running the identity service.
34
35
  *
35
36
  * Nothing in this module is an OAuth2 primitive — only testing support.
36
37
  * Production code should never import from here.
@@ -5,14 +5,35 @@ export interface FakeTenantUser {
5
5
  id: string;
6
6
  /** The `username` introspection reports. */
7
7
  username: string;
8
- /** The `name` claim UserInfo reports. */
8
+ /**
9
+ * The `name` claim UserInfo reports, and the name the organization API's
10
+ * member listing gives the person. The listing falls back to `username`.
11
+ */
9
12
  name?: string;
10
- /** The `email` claim UserInfo reports. */
13
+ /**
14
+ * The `email` claim UserInfo reports. An invitation to this address offers
15
+ * the person a pending membership rather than an invitation.
16
+ */
11
17
  email?: string;
12
- /** The `email_verified` claim UserInfo reports. Defaults to `true`. */
18
+ /**
19
+ * The `email_verified` claim UserInfo reports. Defaults to `true`. While it
20
+ * is `false`, invitations to the address stay hidden from the person's
21
+ * offers and cannot be accepted.
22
+ */
13
23
  emailVerified?: boolean;
14
24
  /** Permissions held tenant-wide, in every organization and on every resource. */
15
25
  permissions?: string[];
26
+ /**
27
+ * The person's own metadata bucket, as `GET /api/account` first reports
28
+ * it. Defaults to `{}`.
29
+ */
30
+ userMetadata?: Record<string, unknown>;
31
+ /**
32
+ * Whether the person has a password. Defaults to `true`. Without one, a
33
+ * linked account is their only way in, and the account API refuses to
34
+ * disconnect the last of them.
35
+ */
36
+ hasPassword?: boolean;
16
37
  }
17
38
  /** An OAuth2 application registered in a {@linkcode FakeTenant}. */
18
39
  export interface FakeTenantClient extends ClientInterface {
@@ -25,6 +46,14 @@ export interface FakeTenantClient extends ClientInterface {
25
46
  accessTokenFormat?: "opaque" | "jwt";
26
47
  /** A JWT access token's `aud`. Defaults to the client id. */
27
48
  audience?: string;
49
+ /**
50
+ * Whose application this is. Defaults to `"first-party"`: its credentials
51
+ * belong to the login session they were issued from, so a later sign-in in
52
+ * that browser revokes them, revoking one ends the session, and ending the
53
+ * session revokes them. A `"third-party"` application's credentials only
54
+ * name that session as the one they came from, and outlive it.
55
+ */
56
+ type?: "first-party" | "third-party";
28
57
  }
29
58
  /** An organization in a {@linkcode FakeTenant}. */
30
59
  export interface FakeTenantOrganization {
@@ -38,8 +67,12 @@ export interface FakeTenantOrganization {
38
67
  /** A person's place in one organization. */
39
68
  export interface FakeTenantMembership {
40
69
  /**
41
- * The membership's roles, as `org_roles` and `/api/memberships` report
42
- * them — `member`, `admin` or `owner` on a real tenant.
70
+ * The roles the membership holds, as `org_roles`, `/api/memberships` and the
71
+ * organization API report them. `owner`, `admin` and `member` are the
72
+ * built-in tiers. `owner` and `admin` may manage the organization through
73
+ * its API, and only `owner` may delete it or deal in the `owner` tier.
74
+ * Defaults to `["member"]`, because a membership on a real tenant always
75
+ * holds a role. An empty list is also read as `["member"]`.
43
76
  */
44
77
  roles?: string[];
45
78
  /** Permissions held inside this organization only. */
@@ -65,6 +98,22 @@ export interface FakeTenantGrant {
65
98
  /** What the subject may do on that one resource. */
66
99
  permissions: string[];
67
100
  }
101
+ /** An organization role your tenant defines beside the built-in tiers. */
102
+ export interface FakeTenantOrganizationRole {
103
+ /** The role a membership, an invitation or `org_roles` names. */
104
+ slug: string;
105
+ /** How the role is named to people. Defaults to the slug. */
106
+ name?: string;
107
+ }
108
+ /** An external account a person can sign in with. */
109
+ export interface FakeTenantLinkedAccount {
110
+ /** The provider's key, such as `google` or `github`. */
111
+ provider: string;
112
+ /** How the provider is named to the person. Defaults to the key. */
113
+ displayName?: string;
114
+ /** The address the provider reported, if any. */
115
+ email?: string | null;
116
+ }
68
117
  /** How the next sign-in resolves. */
69
118
  export interface FakeTenantSignIn {
70
119
  /**
@@ -77,6 +126,10 @@ export interface FakeTenantSignIn {
77
126
  * `invalid_request` when the person is not a member.
78
127
  */
79
128
  organizationId?: string;
129
+ /** The browser `GET /api/account/sessions` reports for this sign-in. */
130
+ userAgent?: string;
131
+ /** The address `GET /api/account/sessions` reports for this sign-in. */
132
+ ipAddress?: string;
80
133
  }
81
134
  /** What {@linkcode FakeTenant.issueAccessToken} mints a token for. */
82
135
  export interface FakeTenantTokenRequest {
@@ -105,9 +158,10 @@ export interface FakeTenantOptions {
105
158
  * A stand-in for a Udibo Identity tenant, for testing an app that signs in against one
106
159
  * without running the identity service. It answers the tenant's protocol
107
160
  * surface — discovery, authorize, token, introspection, UserInfo, JWKS — the
108
- * caller's own `GET /api/memberships`, and the two authorization questions,
109
- * `POST /api/check` and `POST /api/check/batch`, with the same shapes a real
110
- * tenant uses.
161
+ * caller's own `GET /api/memberships`, the two authorization questions,
162
+ * `POST /api/check` and `POST /api/check/batch`, the organization API under
163
+ * `/api/organizations`, and the account API under `/api/account`, with the
164
+ * same shapes and refusals a real tenant uses.
111
165
  *
112
166
  * Nothing here is interactive: {@linkcode signInAs} decides who the next
113
167
  * authorization request authenticates, and there is no consent step. Serve
@@ -125,30 +179,48 @@ export interface FakeTenant {
125
179
  /** Adds an organization. Throws on a duplicate id. */
126
180
  addOrganization(organization: FakeTenantOrganization): void;
127
181
  /**
128
- * Makes a person a member of an organization, replacing any earlier
129
- * membership. Throws when the organization does not exist.
182
+ * Makes a person a member of an organization, replacing earlier membership
183
+ * grants, including pending grants. Email-address invitations remain.
184
+ * Throws when the organization does not exist.
130
185
  */
131
186
  addMember(organizationId: string, userId: string, membership?: FakeTenantMembership): void;
132
187
  /**
133
- * Ends a membership. From then on introspection and `/api/check` stop
134
- * answering for that organization on credentials issued in it, and UserInfo
135
- * drops its `org_*` claims; a JWT access token or id_token already minted
136
- * keeps its claims until it expires.
188
+ * Ends a membership and removes the person's pending membership grants.
189
+ * Email-address invitations remain. From then on introspection and
190
+ * `/api/check` stop answering for that
191
+ * organization on credentials issued in it, and UserInfo drops its `org_*`
192
+ * claims; a JWT access token or id_token already minted keeps its claims
193
+ * until it expires.
137
194
  */
138
195
  removeMember(organizationId: string, userId: string): void;
196
+ /**
197
+ * Defines an organization role beside the built-in tiers, so invitations
198
+ * may offer it and `member-roles` lists it. Throws on a built-in slug.
199
+ */
200
+ defineOrganizationRole(role: FakeTenantOrganizationRole): void;
139
201
  /** Registers a resource type, so `/api/check` accepts it. */
140
202
  registerResourceType(type: string): void;
141
203
  /** Adds a resource grant. */
142
204
  grant(grant: FakeTenantGrant): void;
205
+ /**
206
+ * Links an external account to a person, as the account API lists it, and
207
+ * returns its id. Throws when the person does not exist.
208
+ */
209
+ linkAccount(userId: string, account: FakeTenantLinkedAccount): string;
143
210
  /**
144
211
  * Chooses who the next authorization requests authenticate as, until
145
212
  * called again. With nobody chosen, authorize answers `access_denied`.
213
+ *
214
+ * Each call is a new browser: the first authorization after it starts a
215
+ * login session, which the account API lists, and later ones reuse it until
216
+ * that session is ended. When a first-party application signs in again in
217
+ * that browser, the credentials the session issued before are revoked.
146
218
  */
147
219
  signInAs(userId: string | null, signIn?: FakeTenantSignIn): void;
148
220
  /**
149
221
  * Mints an access token without the browser flow, for testing an API
150
- * directly. JWT or opaque as the client is registered. Throws when the
151
- * client or user does not exist.
222
+ * directly. JWT or opaque as the client is registered. It belongs to no
223
+ * login session. Throws when the client or user does not exist.
152
224
  */
153
225
  issueAccessToken(request: FakeTenantTokenRequest): Promise<string>;
154
226
  }