@owlmeans/server-auth-identity 0.1.18-rc.28 → 0.1.18-rc.29
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 +29 -3
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-auth-identity/SKILL.md +35 -3
- package/build/consts.d.ts +1 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +2 -0
- package/build/consts.js.map +1 -1
- package/build/events.d.ts +9 -0
- package/build/events.d.ts.map +1 -0
- package/build/events.js +29 -0
- package/build/events.js.map +1 -0
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +6 -0
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +15 -0
- package/build/service.js.map +1 -1
- package/build/types.d.ts +37 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/consts.ts +2 -0
- package/src/events.ts +39 -0
- package/src/helper.ts +6 -0
- package/src/index.ts +1 -0
- package/src/service.ts +16 -0
- package/src/types.ts +39 -1
- package/tests/context.ts +98 -0
- package/tests/events.spec.ts +81 -0
- package/tests/linking.spec.ts +1 -70
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ whose grants decide access (use `@owlmeans/server-oidc-rp`'s guard and gate, or
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
bun add @owlmeans/server-auth-identity@^0.1.18-rc.
|
|
13
|
+
bun add @owlmeans/server-auth-identity@^0.1.18-rc.29
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Concepts
|
|
@@ -28,6 +28,8 @@ bun add @owlmeans/server-auth-identity@^0.1.18-rc.28
|
|
|
28
28
|
profile details and returns an `AuthPayload`.
|
|
29
29
|
- **Entity resolver** — the `EntityResolverService` registered under `ENTITY_RESOLVER`. Registering
|
|
30
30
|
it tells the server boundary that this deployment has organizations.
|
|
31
|
+
- **Identity events** — `identityEvents(ctx)` returns the `IdentityEventsService`; its
|
|
32
|
+
`onEntityCreated` listeners run once per newly registered organization entity.
|
|
31
33
|
|
|
32
34
|
## Usage
|
|
33
35
|
|
|
@@ -145,6 +147,23 @@ payload ??= await linking.linkProfile(details, { username: 'person@example.org'
|
|
|
145
147
|
// payload: { type, role, userId, profileId, entitySlug, scopes }
|
|
146
148
|
```
|
|
147
149
|
|
|
150
|
+
### Provision when an organization is created
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { identityEvents } from '@owlmeans/server-auth-identity'
|
|
154
|
+
|
|
155
|
+
// in makeContext, after appendAuthIdentityResources
|
|
156
|
+
identityEvents(context)?.onEntityCreated(async (event, ctx) => {
|
|
157
|
+
await ctx.service<PlanService>(PLAN_SERVICE).grantStarterPlan(event.entityId)
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The event carries `entityId`, `entitySlug`, `iamKey`, `accountId`, `profileId`, `username`, the
|
|
162
|
+
login `type` and `service`, and `createdAt`. It fires only when `linkProfile` registers a new
|
|
163
|
+
identity (including `force: true`) — never when a second sign-in method links to an identity that
|
|
164
|
+
exists. Listeners run in order and are awaited; one that throws is logged and never fails the
|
|
165
|
+
sign-in, so anything a listener provisions needs its own backfill.
|
|
166
|
+
|
|
148
167
|
### Rename an organization and mint a durable name
|
|
149
168
|
|
|
150
169
|
```ts
|
|
@@ -168,13 +187,15 @@ const namespace = await resolver.mintName(entityId, 'namespace', entity => `my-a
|
|
|
168
187
|
|
|
169
188
|
| Symbol | Kind | Purpose |
|
|
170
189
|
|---|---|---|
|
|
171
|
-
| `appendAuthIdentityResources(context, dbAlias?)` | function | Register the four resources, the linking service
|
|
190
|
+
| `appendAuthIdentityResources(context, dbAlias?)` | function | Register the four resources, the linking service, the entity resolver and (unless one is registered) the identity-events service |
|
|
172
191
|
| `makeOrgEntityResource(dbAlias?)` | function | Mongo resource for `OrgEntity` |
|
|
173
192
|
| `makeIdentityAccountResource(dbAlias?)` | function | Mongo resource for `IdentityAccount` |
|
|
174
193
|
| `makeIdentityProfileResource(dbAlias?)` | function | Mongo resource for `IdentityProfile` |
|
|
175
194
|
| `makeIdentityCredentialsResource(dbAlias?)` | function | Mongo resource for `IdentityCredentials` |
|
|
176
195
|
| `makeIdentityLinkingService()` | function | The `IdentityLinkingService` implementation |
|
|
177
196
|
| `makeEntityResolverService(alias = ENTITY_RESOLVER)` | function | The `EntityResolverService` implementation, cached 30 s per resolved name |
|
|
197
|
+
| `makeIdentityEventsService(alias = AUTH_IDENTITY_EVENTS)` | function | The `IdentityEventsService` implementation (lazy) |
|
|
198
|
+
| `identityEvents(ctx, alias?)` | function | The registered events service, or `null` |
|
|
178
199
|
|
|
179
200
|
### Constants
|
|
180
201
|
|
|
@@ -182,6 +203,7 @@ const namespace = await resolver.mintName(entityId, 'namespace', entity => `my-a
|
|
|
182
203
|
|---|---|---|
|
|
183
204
|
| `AUTH_IDENTITY_ORG_ENTITY`, `AUTH_IDENTITY_ACCOUNT`, `AUTH_IDENTITY_PROFILE`, `AUTH_IDENTITY_CREDENTIALS` | const | Resource aliases (`'auth-identity:…'`), lookup keys only |
|
|
184
205
|
| `AUTH_IDENTITY_LINKING` | const | `'auth-identity:linking'` — linking service alias |
|
|
206
|
+
| `AUTH_IDENTITY_EVENTS` | const | `'auth-identity:events'` — identity-events service alias |
|
|
185
207
|
| `AUTH_IDENTITY_DB_ALIAS` | const | `'auth-identity'` — suggested db config alias |
|
|
186
208
|
| `AUTH_IDENTITY_ORG_ENTITY_COLLECTION`, `AUTH_IDENTITY_ACCOUNT_COLLECTION`, `AUTH_IDENTITY_PROFILE_COLLECTION`, `AUTH_IDENTITY_CREDENTIALS_COLLECTION` | const | Colon-free Mongo collection base names |
|
|
187
209
|
| `MAX_ENTITY_SLUG_ATTEMPTS` | const | `8` — word slugs tried before minting gives up |
|
|
@@ -197,6 +219,8 @@ const namespace = await resolver.mintName(entityId, 'namespace', entity => `my-a
|
|
|
197
219
|
| `IdentityCredentials` | type | `AuthCredentials` + `profileId` |
|
|
198
220
|
| `IdentityLinkingService` | type | `getLinkedProfile`, `linkProfile`, `linkCredentials`, `unlinkCredentials`, `getOwnerProfiles`, `getOwnerCredentials` |
|
|
199
221
|
| `AccountMeta` | type | `{ username, force? }` |
|
|
222
|
+
| `IdentityEventsService` | type | `onEntityCreated(callback)`, `propagateEntityCreated(event)` |
|
|
223
|
+
| `EntityCreatedEvent`, `EntityCreatedCallback` | type | The entity-created payload; `(event, ctx) => Promise<void>` |
|
|
200
224
|
| `OrgEntityResource`, `IdentityAccountResource`, `IdentityProfileResource`, `IdentityCredentialsResource` | type | Typed `MongoResource` aliases |
|
|
201
225
|
| `IdentityConfig`, `IdentityContext` | type | Server config and context shapes |
|
|
202
226
|
| `GoogleUserInfo` | type | Google userinfo claims |
|
|
@@ -224,6 +248,8 @@ const namespace = await resolver.mintName(entityId, 'namespace', entity => `my-a
|
|
|
224
248
|
is needed.
|
|
225
249
|
- `linkProfile(details, { username, force: true })` always registers a new identity — use it only
|
|
226
250
|
when a separate identity is intended.
|
|
251
|
+
- Provisioning in an `onEntityCreated` listener is best-effort: a throwing listener is logged and the
|
|
252
|
+
sign-in succeeds, so reconcile what it provisions periodically.
|
|
227
253
|
- The resolver caches for 30 seconds, so a rename reaches other replicas within that window; the old
|
|
228
254
|
slug keeps resolving meanwhile.
|
|
229
255
|
- `rename` rejects a malformed slug and any slug an entity has ever answered to; `mintSlug` throws
|
|
@@ -247,7 +273,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
247
273
|
your project's skill store (`.agents/skills/`):
|
|
248
274
|
|
|
249
275
|
```sh
|
|
250
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
276
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.28
|
|
251
277
|
```
|
|
252
278
|
|
|
253
279
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-auth-identity",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.29",
|
|
5
|
+
"generatedAt": "2026-09-16T17:32:31.932Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-auth-identity
|
|
3
|
-
description: How to use @owlmeans/server-auth-identity — the Mongo-backed local identity store behind provider logins. Registers the organization-entity registry plus account, profile and credentials resources, the IdentityLinkingService that maps an external provider account onto one local identity,
|
|
3
|
+
description: How to use @owlmeans/server-auth-identity — the Mongo-backed local identity store behind provider logins. Registers the organization-entity registry plus account, profile and credentials resources, the IdentityLinkingService that maps an external provider account onto one local identity, the EntityResolverService that turns the slug on a token into a stable entity id, and the identity-events seam that announces a newly created organization entity. Auto-invoked when importing appendAuthIdentityResources, an identity resource alias, IdentityLinkingService, the entity resolver or identityEvents.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-auth-identity
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-auth-identity": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/server-auth-identity": "^0.1.18-rc.29"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The local identity store a deployment owns when it does not delegate identity to an external IAM.
|
|
14
14
|
It answers two questions: *who is this person here* (account / profile / credentials) and *which
|
|
@@ -18,18 +18,21 @@ organization entity is this* (the org-entity registry and its resolver).
|
|
|
18
18
|
|
|
19
19
|
| Export | Description |
|
|
20
20
|
|--------|-------------|
|
|
21
|
-
| `appendAuthIdentityResources(context, dbAlias?)` | Register the four resources, the linking service
|
|
21
|
+
| `appendAuthIdentityResources(context, dbAlias?)` | Register the four resources, the linking service, the entity resolver and — unless one is already registered — the identity-events service |
|
|
22
22
|
| `makeOrgEntityResource(dbAlias?)` | Mongo resource for `OrgEntity` — the organization-entity registry |
|
|
23
23
|
| `makeIdentityAccountResource(dbAlias?)` | Mongo resource for `IdentityAccount` (one per person) |
|
|
24
24
|
| `makeIdentityProfileResource(dbAlias?)` | Mongo resource for `IdentityProfile` (one person inside one organization entity, with a role) |
|
|
25
25
|
| `makeIdentityCredentialsResource(dbAlias?)` | Mongo resource for `IdentityCredentials` (one provider link per profile) |
|
|
26
26
|
| `makeIdentityLinkingService()` | The `IdentityLinkingService` implementation |
|
|
27
27
|
| `makeEntityResolverService(alias?)` | The `EntityResolverService` implementation, cached for 30 s per resolved value |
|
|
28
|
+
| `makeIdentityEventsService(alias?)` | The lazy `IdentityEventsService` implementation |
|
|
29
|
+
| `identityEvents(ctx, alias?)` | The registered events service, or `null` |
|
|
28
30
|
| `AUTH_IDENTITY_ORG_ENTITY` | Resource alias `'auth-identity:org-entity'` |
|
|
29
31
|
| `AUTH_IDENTITY_ACCOUNT` | Resource alias `'auth-identity:account'` |
|
|
30
32
|
| `AUTH_IDENTITY_PROFILE` | Resource alias `'auth-identity:profile'` |
|
|
31
33
|
| `AUTH_IDENTITY_CREDENTIALS` | Resource alias `'auth-identity:credentials'` |
|
|
32
34
|
| `AUTH_IDENTITY_LINKING` | Service alias `'auth-identity:linking'` |
|
|
35
|
+
| `AUTH_IDENTITY_EVENTS` | Service alias `'auth-identity:events'` |
|
|
33
36
|
| `AUTH_IDENTITY_DB_ALIAS` | `'auth-identity'` — the suggested db-config alias whose `resourcePrefix` scopes the prefix to identity collections only |
|
|
34
37
|
| `AUTH_IDENTITY_ORG_ENTITY_COLLECTION`, `AUTH_IDENTITY_ACCOUNT_COLLECTION`, `AUTH_IDENTITY_PROFILE_COLLECTION`, `AUTH_IDENTITY_CREDENTIALS_COLLECTION` | Colon-free Mongo collection base names (`org-entity`, `account`, `profile`, `credentials`) |
|
|
35
38
|
| `MAX_ENTITY_SLUG_ATTEMPTS` | How many word slugs to try before giving up minting a free one |
|
|
@@ -49,6 +52,9 @@ collection names are the colon-free `*_COLLECTION` values, optionally prefixed b
|
|
|
49
52
|
| `IdentityCredentials` | `AuthCredentials` + `profileId` |
|
|
50
53
|
| `IdentityLinkingService` | `getLinkedProfile`, `linkProfile`, `linkCredentials`, `getOwnerProfiles`, `getOwnerCredentials` |
|
|
51
54
|
| `AccountMeta` | `{ username, force? }` — the second argument to `linkProfile` |
|
|
55
|
+
| `IdentityEventsService` | `onEntityCreated(callback)`, `propagateEntityCreated(event)` |
|
|
56
|
+
| `EntityCreatedEvent` | `entityId`, `entitySlug`, `iamKey`, `accountId`, `profileId`, `username`, `type`, `service`, `createdAt` |
|
|
57
|
+
| `EntityCreatedCallback` | `(event, ctx) => Promise<void>` |
|
|
52
58
|
| `OrgEntityResource`, `IdentityAccountResource`, `IdentityProfileResource`, `IdentityCredentialsResource` | Typed `MongoResource` aliases |
|
|
53
59
|
| `IdentityConfig`, `IdentityContext` | Server config/context shapes |
|
|
54
60
|
| `GoogleUserInfo` | The Google userinfo claim set |
|
|
@@ -154,6 +160,32 @@ profile carrying one is a platform login and a profile carrying none is somebody
|
|
|
154
160
|
`linkProfile(details, { username, force: true })` skips the lookup and registers a fresh identity
|
|
155
161
|
regardless. Use it only where a genuinely separate identity is intended.
|
|
156
162
|
|
|
163
|
+
## Identity events
|
|
164
|
+
|
|
165
|
+
`linkProfile` is the only place an organization entity is minted, so it is where one is announced:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { identityEvents } from '@owlmeans/server-auth-identity'
|
|
169
|
+
|
|
170
|
+
// while wiring the context, after appendAuthIdentityResources — the service is lazy
|
|
171
|
+
identityEvents(context)?.onEntityCreated(async (event, ctx) => {
|
|
172
|
+
await provisionStarterPlan(ctx, event.entityId)
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **It fires at the end of the REGISTRATION branch only** — after the entity, account, profile and
|
|
177
|
+
credential rows exist, and on `force: true` too. A second sign-in method that links to a known
|
|
178
|
+
identity creates no entity and fires nothing. Provisioning keyed on "reached `linkProfile`"
|
|
179
|
+
would double-fund that person; key it on this event.
|
|
180
|
+
- **Listeners run in registration order and are awaited**, so the sign-in completes after them.
|
|
181
|
+
Keep them short: one row, one enqueue.
|
|
182
|
+
- **A throwing listener is logged and never fails the sign-in**, and the next listener still runs.
|
|
183
|
+
The person is registered either way, so whatever a listener provisions needs a periodic
|
|
184
|
+
reconciliation to backfill the misses.
|
|
185
|
+
- **`identityEvents(ctx)` answers `null` where the service is not registered** — write `?.` and a
|
|
186
|
+
deployment without the seam pays nothing.
|
|
187
|
+
- Key what a listener writes on `event.entityId`, never on `entitySlug`: the slug can be renamed.
|
|
188
|
+
|
|
157
189
|
## Key derivation conventions
|
|
158
190
|
|
|
159
191
|
- **Account `credential`** — a unique 16-character Base58 slug, the account's own stable key.
|
package/build/consts.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const AUTH_IDENTITY_PROFILE = "auth-identity:profile";
|
|
|
3
3
|
export declare const AUTH_IDENTITY_CREDENTIALS = "auth-identity:credentials";
|
|
4
4
|
export declare const AUTH_IDENTITY_LINKING = "auth-identity:linking";
|
|
5
5
|
export declare const AUTH_IDENTITY_ORG_ENTITY = "auth-identity:org-entity";
|
|
6
|
+
export declare const AUTH_IDENTITY_EVENTS = "auth-identity:events";
|
|
6
7
|
export declare const AUTH_IDENTITY_ACCOUNT_COLLECTION = "account";
|
|
7
8
|
export declare const AUTH_IDENTITY_PROFILE_COLLECTION = "profile";
|
|
8
9
|
export declare const AUTH_IDENTITY_CREDENTIALS_COLLECTION = "credentials";
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAElE,eAAO,MAAM,oBAAoB,yBAAyB,CAAA;AAM1D,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,oCAAoC,gBAAgB,CAAA;AACjE,eAAO,MAAM,mCAAmC,eAAe,CAAA;AAI/D,eAAO,MAAM,sBAAsB,kBAAkB,CAAA;AAIrD,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAGzC,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C,eAAO,MAAM,sBAAsB,MAAM,CAAA"}
|
package/build/consts.js
CHANGED
|
@@ -7,6 +7,8 @@ export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile';
|
|
|
7
7
|
export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials';
|
|
8
8
|
export const AUTH_IDENTITY_LINKING = 'auth-identity:linking';
|
|
9
9
|
export const AUTH_IDENTITY_ORG_ENTITY = 'auth-identity:org-entity';
|
|
10
|
+
// Service alias of the identity-events seam (`identityEvents(ctx)`).
|
|
11
|
+
export const AUTH_IDENTITY_EVENTS = 'auth-identity:events';
|
|
10
12
|
// Colon-free Mongo collection base names. The final collection name is
|
|
11
13
|
// `${resourcePrefix}${collection}` — e.g. with the project prefix 'viam-' this yields
|
|
12
14
|
// 'viam-account'. Register the identity resources against a db config that carries the
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,wFAAwF;AACxF,sFAAsF;AACtF,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,wFAAwF;AACxF,sFAAsF;AACtF,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;AAClE,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,uEAAuE;AACvE,sFAAsF;AACtF,uFAAuF;AACvF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,oCAAoC,GAAG,aAAa,CAAA;AACjE,MAAM,CAAC,MAAM,mCAAmC,GAAG,YAAY,CAAA;AAE/D,mFAAmF;AACnF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAA;AAErD,iGAAiG;AACjG,uEAAuE;AACvE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAEzC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAC7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context';
|
|
2
|
+
import type { IdentityEventsService } from './types.js';
|
|
3
|
+
export declare const makeIdentityEventsService: (alias?: string) => IdentityEventsService;
|
|
4
|
+
/**
|
|
5
|
+
* The identity-events service, or `null` where the context has none registered — so a caller
|
|
6
|
+
* writes `identityEvents(ctx)?.onEntityCreated(...)` and a deployment without the seam pays nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare const identityEvents: (ctx: BasicContext<any>, alias?: string) => IdentityEventsService | null;
|
|
9
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrD,OAAO,KAAK,EAA0C,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAE/F,eAAO,MAAM,yBAAyB,WAC7B,MAAM,KACZ,qBAsBF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,QACpB,YAAY,CAAC,GAAG,CAAC,UAAS,MAAM,KACpC,qBAAqB,GAAG,IAC+C,CAAA"}
|
package/build/events.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createLazyService } from '@owlmeans/context';
|
|
2
|
+
import { AUTH_IDENTITY_EVENTS } from './consts.js';
|
|
3
|
+
export const makeIdentityEventsService = (alias = AUTH_IDENTITY_EVENTS) => {
|
|
4
|
+
const created = [];
|
|
5
|
+
const service = createLazyService(alias, {
|
|
6
|
+
onEntityCreated: callback => {
|
|
7
|
+
created.push(callback);
|
|
8
|
+
},
|
|
9
|
+
propagateEntityCreated: async (event) => {
|
|
10
|
+
for (const callback of created) {
|
|
11
|
+
try {
|
|
12
|
+
await callback(event, service.assertCtx());
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// The entity, the account and the profile already exist; failing here would turn a
|
|
16
|
+
// listener's problem into a sign-in that never completes for a person who is registered.
|
|
17
|
+
console.error(`${alias}: entity-created listener failed for ${event.entityId}`, error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
return service;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The identity-events service, or `null` where the context has none registered — so a caller
|
|
26
|
+
* writes `identityEvents(ctx)?.onEntityCreated(...)` and a deployment without the seam pays nothing.
|
|
27
|
+
*/
|
|
28
|
+
export const identityEvents = (ctx, alias = AUTH_IDENTITY_EVENTS) => ctx.hasService(alias) ? ctx.service(alias) : null;
|
|
29
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGlD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,KAAK,GAAW,oBAAoB,EACb,EAAE;IACzB,MAAM,OAAO,GAA4B,EAAE,CAAA;IAE3C,MAAM,OAAO,GAA0B,iBAAiB,CAAwB,KAAK,EAAE;QACrF,eAAe,EAAE,QAAQ,CAAC,EAAE;YAC1B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACxB,CAAC;QAED,sBAAsB,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACpC,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,EAA2C,CAAC,CAAA;gBACrF,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,mFAAmF;oBACnF,yFAAyF;oBACzF,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,wCAAwC,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAA;gBACxF,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,GAAsB,EAAE,KAAK,GAAW,oBAAoB,EAC9B,EAAE,CAChC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAwB,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA"}
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAOrD,eAAO,MAAM,2BAA2B,YAC7B,YAAY,CAAC,GAAG,CAAC,YAChB,MAAM,KACf,IAcF,CAAA"}
|
package/build/helper.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource, makeOrgEntityResource } from './resource.js';
|
|
2
2
|
import { makeIdentityLinkingService } from './service.js';
|
|
3
3
|
import { makeEntityResolverService } from './resolver.js';
|
|
4
|
+
import { makeIdentityEventsService } from './events.js';
|
|
5
|
+
import { AUTH_IDENTITY_EVENTS } from './consts.js';
|
|
4
6
|
export const appendAuthIdentityResources = (context, dbAlias) => {
|
|
5
7
|
context.registerResource(makeIdentityAccountResource(dbAlias));
|
|
6
8
|
context.registerResource(makeIdentityProfileResource(dbAlias));
|
|
@@ -11,5 +13,9 @@ export const appendAuthIdentityResources = (context, dbAlias) => {
|
|
|
11
13
|
// organizations: without it `request.entity` stays undefined and every consumer falls back to
|
|
12
14
|
// treating the token's slug as the only entity value there is.
|
|
13
15
|
context.registerService(makeEntityResolverService());
|
|
16
|
+
// Behind `hasService`, so an application that registered its own events service first keeps it.
|
|
17
|
+
if (!context.hasService(AUTH_IDENTITY_EVENTS)) {
|
|
18
|
+
context.registerService(makeIdentityEventsService());
|
|
19
|
+
}
|
|
14
20
|
};
|
|
15
21
|
//# sourceMappingURL=helper.js.map
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAChJ,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAChJ,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAA0B,EAC1B,OAAgB,EACV,EAAE;IACR,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC,CAAA;IAClE,OAAO,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;IACxD,OAAO,CAAC,eAAe,CAAC,0BAA0B,EAAE,CAAC,CAAA;IACrD,sFAAsF;IACtF,8FAA8F;IAC9F,+DAA+D;IAC/D,OAAO,CAAC,eAAe,CAAC,yBAAyB,EAAE,CAAC,CAAA;IACpD,gGAAgG;IAChG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC9C,OAAO,CAAC,eAAe,CAAC,yBAAyB,EAAE,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAA"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAE3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAE3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAoBpJ,eAAO,MAAM,0BAA0B,QAAO,sBA6R7C,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ALL_SCOPES, AuthRole } from '@owlmeans/auth';
|
|
|
3
3
|
import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids';
|
|
4
4
|
import { ENTITY_RESOLVER } from '@owlmeans/auth-common';
|
|
5
5
|
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, AUTH_IDENTITY_ORG_ENTITY, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js';
|
|
6
|
+
import { identityEvents } from './events.js';
|
|
6
7
|
const MAX_SLUG_RETRIES = 5;
|
|
7
8
|
// Stable external login key: "{type}:{service}:{providerSub}"
|
|
8
9
|
const externalKey = (details) => [details.type, details.service, details.userId].join(EXTERNAL_KEY_DELIMITER);
|
|
@@ -172,6 +173,20 @@ export const makeIdentityLinkingService = () => {
|
|
|
172
173
|
profileId,
|
|
173
174
|
credential: svc,
|
|
174
175
|
});
|
|
176
|
+
// Only this branch creates an organization — the linking branch above adds a credential to
|
|
177
|
+
// one that exists — so this is the one place an entity is announced, once it is complete.
|
|
178
|
+
// Listeners are awaited and never throw (the service logs them).
|
|
179
|
+
await identityEvents(ctx)?.propagateEntityCreated({
|
|
180
|
+
entityId,
|
|
181
|
+
entitySlug: entity.slug,
|
|
182
|
+
iamKey: entity.iamKey,
|
|
183
|
+
accountId,
|
|
184
|
+
profileId: profile.profileId,
|
|
185
|
+
username: meta.username,
|
|
186
|
+
type: details.type,
|
|
187
|
+
service: details.service,
|
|
188
|
+
createdAt: entity.createdAt,
|
|
189
|
+
});
|
|
175
190
|
return {
|
|
176
191
|
type: details.type,
|
|
177
192
|
role: profile.role,
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAGpD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAM/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResourceRecord } from '@owlmeans/resource';
|
|
2
2
|
import type { MongoResource } from '@owlmeans/mongo-resource';
|
|
3
|
-
import type { InitializedService } from '@owlmeans/context';
|
|
3
|
+
import type { InitializedService, LazyService } from '@owlmeans/context';
|
|
4
4
|
import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
|
|
5
5
|
import type { AuthCredentials, AuthPayload, AuthRole, Profile } from '@owlmeans/auth';
|
|
6
6
|
import type { ProviderProfileDetails } from '@owlmeans/oidc';
|
|
@@ -101,4 +101,40 @@ export interface GoogleUserInfo {
|
|
|
101
101
|
family_name?: string;
|
|
102
102
|
picture?: string;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* An organization entity was just registered, together with the account and profile of the person
|
|
106
|
+
* whose first sign-in created it. `entityId` is the stable id every per-organization record keys
|
|
107
|
+
* on; `entitySlug` is its name at this moment and may be renamed later.
|
|
108
|
+
*/
|
|
109
|
+
export interface EntityCreatedEvent {
|
|
110
|
+
entityId: string;
|
|
111
|
+
entitySlug: string;
|
|
112
|
+
iamKey: string;
|
|
113
|
+
accountId: string;
|
|
114
|
+
profileId: string;
|
|
115
|
+
username: string;
|
|
116
|
+
/** The login's `AuthenticationType`. */
|
|
117
|
+
type: string;
|
|
118
|
+
/** The provider service alias the login came through. */
|
|
119
|
+
service: string;
|
|
120
|
+
createdAt: Date;
|
|
121
|
+
}
|
|
122
|
+
export interface EntityCreatedCallback {
|
|
123
|
+
(event: EntityCreatedEvent, ctx: IdentityContext): Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Lifecycle notifications from the identity store — the seam an application hangs provisioning on
|
|
127
|
+
* (a starting plan, a default workspace) without wrapping the linking service.
|
|
128
|
+
*
|
|
129
|
+
* Lazy, so a listener can be registered while the context is still being wired.
|
|
130
|
+
*/
|
|
131
|
+
export interface IdentityEventsService extends LazyService {
|
|
132
|
+
/** Called for every entity registered from now on, in registration order. */
|
|
133
|
+
onEntityCreated: (callback: EntityCreatedCallback) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Run every listener, one after another, each awaited. A listener that throws is logged and the
|
|
136
|
+
* rest still run: a listener's failure never fails the sign-in that created the entity.
|
|
137
|
+
*/
|
|
138
|
+
propagateEntityCreated: (event: EntityCreatedEvent) => Promise<void>;
|
|
139
|
+
}
|
|
104
140
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/server-auth-identity",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.29",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/basic-ids": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/mongo-resource": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/oidc": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
32
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
25
|
+
"@owlmeans/auth": "^0.1.18-rc.27",
|
|
26
|
+
"@owlmeans/auth-common": "^0.1.18-rc.29",
|
|
27
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.26",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.25",
|
|
29
|
+
"@owlmeans/mongo-resource": "^0.1.18-rc.28",
|
|
30
|
+
"@owlmeans/oidc": "^0.1.18-rc.37",
|
|
31
|
+
"@owlmeans/resource": "^0.1.18-rc.27",
|
|
32
|
+
"@owlmeans/server-context": "^0.1.18-rc.28"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"mongodb": "*"
|
package/src/consts.ts
CHANGED
|
@@ -8,6 +8,8 @@ export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile'
|
|
|
8
8
|
export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials'
|
|
9
9
|
export const AUTH_IDENTITY_LINKING = 'auth-identity:linking'
|
|
10
10
|
export const AUTH_IDENTITY_ORG_ENTITY = 'auth-identity:org-entity'
|
|
11
|
+
// Service alias of the identity-events seam (`identityEvents(ctx)`).
|
|
12
|
+
export const AUTH_IDENTITY_EVENTS = 'auth-identity:events'
|
|
11
13
|
|
|
12
14
|
// Colon-free Mongo collection base names. The final collection name is
|
|
13
15
|
// `${resourcePrefix}${collection}` — e.g. with the project prefix 'viam-' this yields
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
2
|
+
import { createLazyService } from '@owlmeans/context'
|
|
3
|
+
import { AUTH_IDENTITY_EVENTS } from './consts.js'
|
|
4
|
+
import type { EntityCreatedCallback, IdentityContext, IdentityEventsService } from './types.js'
|
|
5
|
+
|
|
6
|
+
export const makeIdentityEventsService = (
|
|
7
|
+
alias: string = AUTH_IDENTITY_EVENTS
|
|
8
|
+
): IdentityEventsService => {
|
|
9
|
+
const created: EntityCreatedCallback[] = []
|
|
10
|
+
|
|
11
|
+
const service: IdentityEventsService = createLazyService<IdentityEventsService>(alias, {
|
|
12
|
+
onEntityCreated: callback => {
|
|
13
|
+
created.push(callback)
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
propagateEntityCreated: async event => {
|
|
17
|
+
for (const callback of created) {
|
|
18
|
+
try {
|
|
19
|
+
await callback(event, service.assertCtx<IdentityContext['cfg'], IdentityContext>())
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// The entity, the account and the profile already exist; failing here would turn a
|
|
22
|
+
// listener's problem into a sign-in that never completes for a person who is registered.
|
|
23
|
+
console.error(`${alias}: entity-created listener failed for ${event.entityId}`, error)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return service
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The identity-events service, or `null` where the context has none registered — so a caller
|
|
34
|
+
* writes `identityEvents(ctx)?.onEntityCreated(...)` and a deployment without the seam pays nothing.
|
|
35
|
+
*/
|
|
36
|
+
export const identityEvents = (
|
|
37
|
+
ctx: BasicContext<any>, alias: string = AUTH_IDENTITY_EVENTS
|
|
38
|
+
): IdentityEventsService | null =>
|
|
39
|
+
ctx.hasService(alias) ? ctx.service<IdentityEventsService>(alias) : null
|
package/src/helper.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { BasicContext } from '@owlmeans/context'
|
|
|
2
2
|
import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource, makeOrgEntityResource } from './resource.js'
|
|
3
3
|
import { makeIdentityLinkingService } from './service.js'
|
|
4
4
|
import { makeEntityResolverService } from './resolver.js'
|
|
5
|
+
import { makeIdentityEventsService } from './events.js'
|
|
6
|
+
import { AUTH_IDENTITY_EVENTS } from './consts.js'
|
|
5
7
|
|
|
6
8
|
export const appendAuthIdentityResources = (
|
|
7
9
|
context: BasicContext<any>,
|
|
@@ -16,4 +18,8 @@ export const appendAuthIdentityResources = (
|
|
|
16
18
|
// organizations: without it `request.entity` stays undefined and every consumer falls back to
|
|
17
19
|
// treating the token's slug as the only entity value there is.
|
|
18
20
|
context.registerService(makeEntityResolverService())
|
|
21
|
+
// Behind `hasService`, so an application that registered its own events service first keeps it.
|
|
22
|
+
if (!context.hasService(AUTH_IDENTITY_EVENTS)) {
|
|
23
|
+
context.registerService(makeIdentityEventsService())
|
|
24
|
+
}
|
|
19
25
|
}
|
package/src/index.ts
CHANGED
package/src/service.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { OrgEntity, OrgEntityResource } from './types.js'
|
|
|
11
11
|
import type { EntityResolverService } from '@owlmeans/auth-common'
|
|
12
12
|
import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
|
|
13
13
|
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, AUTH_IDENTITY_ORG_ENTITY, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js'
|
|
14
|
+
import { identityEvents } from './events.js'
|
|
14
15
|
|
|
15
16
|
type Context = ServerContext<ServerConfig>
|
|
16
17
|
|
|
@@ -200,6 +201,21 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
|
|
|
200
201
|
credential: svc,
|
|
201
202
|
})
|
|
202
203
|
|
|
204
|
+
// Only this branch creates an organization — the linking branch above adds a credential to
|
|
205
|
+
// one that exists — so this is the one place an entity is announced, once it is complete.
|
|
206
|
+
// Listeners are awaited and never throw (the service logs them).
|
|
207
|
+
await identityEvents(ctx)?.propagateEntityCreated({
|
|
208
|
+
entityId,
|
|
209
|
+
entitySlug: entity.slug,
|
|
210
|
+
iamKey: entity.iamKey,
|
|
211
|
+
accountId,
|
|
212
|
+
profileId: profile.profileId,
|
|
213
|
+
username: meta.username,
|
|
214
|
+
type: details.type,
|
|
215
|
+
service: details.service,
|
|
216
|
+
createdAt: entity.createdAt,
|
|
217
|
+
})
|
|
218
|
+
|
|
203
219
|
return {
|
|
204
220
|
type: details.type,
|
|
205
221
|
role: profile.role,
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResourceRecord } from '@owlmeans/resource'
|
|
2
2
|
import type { MongoResource } from '@owlmeans/mongo-resource'
|
|
3
|
-
import type { InitializedService } from '@owlmeans/context'
|
|
3
|
+
import type { InitializedService, LazyService } from '@owlmeans/context'
|
|
4
4
|
import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
|
|
5
5
|
import type { AuthCredentials, AuthPayload, AuthRole, Profile } from '@owlmeans/auth'
|
|
6
6
|
import type { ProviderProfileDetails } from '@owlmeans/oidc'
|
|
@@ -113,3 +113,41 @@ export interface GoogleUserInfo {
|
|
|
113
113
|
picture?: string
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* An organization entity was just registered, together with the account and profile of the person
|
|
118
|
+
* whose first sign-in created it. `entityId` is the stable id every per-organization record keys
|
|
119
|
+
* on; `entitySlug` is its name at this moment and may be renamed later.
|
|
120
|
+
*/
|
|
121
|
+
export interface EntityCreatedEvent {
|
|
122
|
+
entityId: string
|
|
123
|
+
entitySlug: string
|
|
124
|
+
iamKey: string
|
|
125
|
+
accountId: string
|
|
126
|
+
profileId: string
|
|
127
|
+
username: string
|
|
128
|
+
/** The login's `AuthenticationType`. */
|
|
129
|
+
type: string
|
|
130
|
+
/** The provider service alias the login came through. */
|
|
131
|
+
service: string
|
|
132
|
+
createdAt: Date
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface EntityCreatedCallback {
|
|
136
|
+
(event: EntityCreatedEvent, ctx: IdentityContext): Promise<void>
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Lifecycle notifications from the identity store — the seam an application hangs provisioning on
|
|
141
|
+
* (a starting plan, a default workspace) without wrapping the linking service.
|
|
142
|
+
*
|
|
143
|
+
* Lazy, so a listener can be registered while the context is still being wired.
|
|
144
|
+
*/
|
|
145
|
+
export interface IdentityEventsService extends LazyService {
|
|
146
|
+
/** Called for every entity registered from now on, in registration order. */
|
|
147
|
+
onEntityCreated: (callback: EntityCreatedCallback) => void
|
|
148
|
+
/**
|
|
149
|
+
* Run every listener, one after another, each awaited. A listener that throws is logged and the
|
|
150
|
+
* rest still run: a listener's failure never fails the sign-in that created the entity.
|
|
151
|
+
*/
|
|
152
|
+
propagateEntityCreated: (event: EntityCreatedEvent) => Promise<void>
|
|
153
|
+
}
|
package/tests/context.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
|
|
2
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
3
|
+
import { makeIdentityLinkingService } from '../src/service.js'
|
|
4
|
+
import { makeIdentityEventsService } from '../src/events.js'
|
|
5
|
+
import {
|
|
6
|
+
AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_EVENTS, AUTH_IDENTITY_ORG_ENTITY,
|
|
7
|
+
AUTH_IDENTITY_PROFILE,
|
|
8
|
+
} from '../src/consts.js'
|
|
9
|
+
import type { IdentityEventsService } from '../src/types.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The rows are stubbed rather than mocked — the linking service's whole behaviour is which record
|
|
13
|
+
* it reads before it writes, so a stub that records writes is the subject, not a stand-in for it.
|
|
14
|
+
*/
|
|
15
|
+
export const stubResource = <T extends { id?: string }>(alias: string, seed: T[] = []) => {
|
|
16
|
+
const items: T[] = [...seed]
|
|
17
|
+
let next = seed.length + 1
|
|
18
|
+
|
|
19
|
+
const matches = (record: any, where: any): boolean =>
|
|
20
|
+
where == null || Object.entries(where).every(([key, value]) => record[key] === value)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
alias,
|
|
24
|
+
items,
|
|
25
|
+
list: async (where?: unknown) => ({ items: items.filter(item => matches(item, where)) }),
|
|
26
|
+
load: async (where?: unknown) => items.find(item => matches(item, where)) ?? null,
|
|
27
|
+
get: async (where?: unknown) => {
|
|
28
|
+
const found = items.find(item => matches(item, where))
|
|
29
|
+
if (found == null) throw new Error(`${alias}: not found`)
|
|
30
|
+
return found
|
|
31
|
+
},
|
|
32
|
+
create: async (record: T) => {
|
|
33
|
+
const stored = { ...record, id: record.id ?? `${alias}-${next++}` } as T
|
|
34
|
+
items.push(stored)
|
|
35
|
+
return stored
|
|
36
|
+
},
|
|
37
|
+
delete: async (id: string) => {
|
|
38
|
+
const at = items.findIndex(item => item.id === id)
|
|
39
|
+
if (at < 0) return null
|
|
40
|
+
return items.splice(at, 1)[0]
|
|
41
|
+
},
|
|
42
|
+
registerContext: () => undefined,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CtxSeed {
|
|
47
|
+
accounts?: any[]
|
|
48
|
+
profiles?: any[]
|
|
49
|
+
credentials?: any[]
|
|
50
|
+
/** Register the identity-events service, as `appendAuthIdentityResources` does. */
|
|
51
|
+
events?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const makeCtx = (seed: CtxSeed = {}) => {
|
|
55
|
+
const resources: Record<string, any> = {
|
|
56
|
+
[AUTH_IDENTITY_ACCOUNT]: stubResource('account', seed.accounts),
|
|
57
|
+
[AUTH_IDENTITY_PROFILE]: stubResource('profile', seed.profiles),
|
|
58
|
+
[AUTH_IDENTITY_CREDENTIALS]: stubResource('credential', seed.credentials),
|
|
59
|
+
[AUTH_IDENTITY_ORG_ENTITY]: stubResource('entity'),
|
|
60
|
+
}
|
|
61
|
+
const services: Record<string, unknown> = {}
|
|
62
|
+
let minted = 0
|
|
63
|
+
|
|
64
|
+
const ctx = {
|
|
65
|
+
resources,
|
|
66
|
+
resource: (alias: string) => resources[alias],
|
|
67
|
+
hasService: (alias: string) => alias === ENTITY_RESOLVER || alias in services,
|
|
68
|
+
service: (alias: string) => {
|
|
69
|
+
if (alias in services) return services[alias]
|
|
70
|
+
if (alias !== ENTITY_RESOLVER) throw new Error(`unexpected service ${alias}`)
|
|
71
|
+
return {
|
|
72
|
+
mintSlug: async () => `slug-${++minted}`,
|
|
73
|
+
byId: async (id: string) => ({ id, slug: `slug-of-${id}` }),
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (seed.events === true) {
|
|
79
|
+
const events = makeIdentityEventsService()
|
|
80
|
+
events.registerContext(ctx as unknown as BasicContext<any>)
|
|
81
|
+
services[AUTH_IDENTITY_EVENTS] = events
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return ctx
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const eventsOf = (ctx: ReturnType<typeof makeCtx>): IdentityEventsService =>
|
|
88
|
+
ctx.service(AUTH_IDENTITY_EVENTS) as IdentityEventsService
|
|
89
|
+
|
|
90
|
+
export const details = (type: string, sub: string) => ({
|
|
91
|
+
type, service: type, clientId: type, userId: sub, username: 'person@example.org',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
export const linkingFor = (ctx: unknown) => {
|
|
95
|
+
const service = makeIdentityLinkingService()
|
|
96
|
+
service.registerContext(ctx as never)
|
|
97
|
+
return service
|
|
98
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { AuthenticationType } from '@owlmeans/auth'
|
|
3
|
+
import type { EntityCreatedEvent } from '../src/types.js'
|
|
4
|
+
import { identityEvents } from '../src/events.js'
|
|
5
|
+
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_ORG_ENTITY } from '../src/consts.js'
|
|
6
|
+
import { details, eventsOf, linkingFor, makeCtx } from './context.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The organization entity is announced exactly when one is created — on a registration, never on
|
|
10
|
+
* a second sign-in method that links to an identity the platform already has. An application
|
|
11
|
+
* provisions on it (a starting plan, say), so a missed or doubled event is a missed or doubled
|
|
12
|
+
* provisioning, and a listener's failure must not become a login failure.
|
|
13
|
+
*/
|
|
14
|
+
describe('identity events', () => {
|
|
15
|
+
test('a registration announces the entity it created, once', async () => {
|
|
16
|
+
const ctx = makeCtx({ events: true })
|
|
17
|
+
const received: EntityCreatedEvent[] = []
|
|
18
|
+
eventsOf(ctx).onEntityCreated(async event => { received.push(event) })
|
|
19
|
+
|
|
20
|
+
const payload = await linkingFor(ctx).linkProfile(
|
|
21
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const [entity] = ctx.resources[AUTH_IDENTITY_ORG_ENTITY].items
|
|
25
|
+
expect(received).toHaveLength(1)
|
|
26
|
+
expect(received[0]).toMatchObject({
|
|
27
|
+
entityId: entity.id,
|
|
28
|
+
entitySlug: entity.slug,
|
|
29
|
+
iamKey: entity.iamKey,
|
|
30
|
+
profileId: payload.profileId,
|
|
31
|
+
username: 'person@example.org',
|
|
32
|
+
type: 'google-oauth',
|
|
33
|
+
service: 'google-oauth',
|
|
34
|
+
})
|
|
35
|
+
expect(received[0].accountId).toBe(ctx.resources[AUTH_IDENTITY_ACCOUNT].items[0].id)
|
|
36
|
+
expect(received[0].createdAt).toBeInstanceOf(Date)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('a second sign-in method links and announces nothing', async () => {
|
|
40
|
+
const ctx = makeCtx({ events: true })
|
|
41
|
+
const received: EntityCreatedEvent[] = []
|
|
42
|
+
eventsOf(ctx).onEntityCreated(async event => { received.push(event) })
|
|
43
|
+
const linking = linkingFor(ctx)
|
|
44
|
+
|
|
45
|
+
await linking.linkProfile(details('google-oauth', 'google-sub') as never, { username: 'person@example.org' })
|
|
46
|
+
await linking.linkProfile(
|
|
47
|
+
details(AuthenticationType.Supervisor, 'person@example.org') as never, { username: 'person@example.org' }
|
|
48
|
+
)
|
|
49
|
+
expect(received).toHaveLength(1)
|
|
50
|
+
|
|
51
|
+
await linking.linkProfile(
|
|
52
|
+
details('google-oauth', 'other-sub') as never, { username: 'person@example.org', force: true }
|
|
53
|
+
)
|
|
54
|
+
expect(received).toHaveLength(2)
|
|
55
|
+
expect(received[1].entityId).not.toBe(received[0].entityId)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('a throwing listener neither fails the sign-in nor silences the next one', async () => {
|
|
59
|
+
const ctx = makeCtx({ events: true })
|
|
60
|
+
const reached: string[] = []
|
|
61
|
+
eventsOf(ctx).onEntityCreated(async () => { throw new Error('provisioning is down') })
|
|
62
|
+
eventsOf(ctx).onEntityCreated(async event => { reached.push(event.entityId) })
|
|
63
|
+
|
|
64
|
+
const payload = await linkingFor(ctx).linkProfile(
|
|
65
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
expect(payload.profileId).toStartWith('google-oauth:')
|
|
69
|
+
expect(reached).toHaveLength(1)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('without the service registered there is nothing to announce to', async () => {
|
|
73
|
+
const ctx = makeCtx()
|
|
74
|
+
|
|
75
|
+
expect(identityEvents(ctx as never)).toBeNull()
|
|
76
|
+
const payload = await linkingFor(ctx).linkProfile(
|
|
77
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
78
|
+
)
|
|
79
|
+
expect(payload.profileId).toStartWith('google-oauth:')
|
|
80
|
+
})
|
|
81
|
+
})
|
package/tests/linking.spec.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { AuthRole } from '@owlmeans/auth'
|
|
3
3
|
import { AuthenticationType } from '@owlmeans/auth'
|
|
4
|
-
import { makeIdentityLinkingService } from '../src/service.js'
|
|
5
4
|
import {
|
|
6
5
|
AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_ORG_ENTITY,
|
|
7
6
|
AUTH_IDENTITY_PROFILE,
|
|
8
7
|
} from '../src/consts.js'
|
|
9
|
-
import {
|
|
8
|
+
import { details, linkingFor, makeCtx } from './context.js'
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* One human, one email, one platform profile — whichever way they sign in.
|
|
@@ -15,75 +14,7 @@ import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
|
|
|
15
14
|
* and registration is per METHOD. The same address signing in by Google and then by key ended up
|
|
16
15
|
* in two entities that could not see each other's projects: `requireEntity` matched neither, and
|
|
17
16
|
* ownership (`project.createdBy`) named a profile the other identity did not have.
|
|
18
|
-
*
|
|
19
|
-
* The rows are stubbed rather than mocked — the service's whole behaviour here is which record it
|
|
20
|
-
* reads before it writes, so a stub that records writes is the subject, not a stand-in for it.
|
|
21
17
|
*/
|
|
22
|
-
const stubResource = <T extends { id?: string }>(alias: string, seed: T[] = []) => {
|
|
23
|
-
const items: T[] = [...seed]
|
|
24
|
-
let next = seed.length + 1
|
|
25
|
-
|
|
26
|
-
const matches = (record: any, where: any): boolean =>
|
|
27
|
-
where == null || Object.entries(where).every(([key, value]) => record[key] === value)
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
alias,
|
|
31
|
-
items,
|
|
32
|
-
list: async (where?: unknown) => ({ items: items.filter(item => matches(item, where)) }),
|
|
33
|
-
load: async (where?: unknown) => items.find(item => matches(item, where)) ?? null,
|
|
34
|
-
get: async (where?: unknown) => {
|
|
35
|
-
const found = items.find(item => matches(item, where))
|
|
36
|
-
if (found == null) throw new Error(`${alias}: not found`)
|
|
37
|
-
return found
|
|
38
|
-
},
|
|
39
|
-
create: async (record: T) => {
|
|
40
|
-
const stored = { ...record, id: record.id ?? `${alias}-${next++}` } as T
|
|
41
|
-
items.push(stored)
|
|
42
|
-
return stored
|
|
43
|
-
},
|
|
44
|
-
delete: async (id: string) => {
|
|
45
|
-
const at = items.findIndex(item => item.id === id)
|
|
46
|
-
if (at < 0) return null
|
|
47
|
-
return items.splice(at, 1)[0]
|
|
48
|
-
},
|
|
49
|
-
registerContext: () => undefined,
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const makeCtx = (seed: {
|
|
54
|
-
accounts?: any[], profiles?: any[], credentials?: any[]
|
|
55
|
-
} = {}) => {
|
|
56
|
-
const resources: Record<string, any> = {
|
|
57
|
-
[AUTH_IDENTITY_ACCOUNT]: stubResource('account', seed.accounts),
|
|
58
|
-
[AUTH_IDENTITY_PROFILE]: stubResource('profile', seed.profiles),
|
|
59
|
-
[AUTH_IDENTITY_CREDENTIALS]: stubResource('credential', seed.credentials),
|
|
60
|
-
[AUTH_IDENTITY_ORG_ENTITY]: stubResource('entity'),
|
|
61
|
-
}
|
|
62
|
-
let minted = 0
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
resources,
|
|
66
|
-
resource: (alias: string) => resources[alias],
|
|
67
|
-
service: (alias: string) => {
|
|
68
|
-
if (alias !== ENTITY_RESOLVER) throw new Error(`unexpected service ${alias}`)
|
|
69
|
-
return {
|
|
70
|
-
mintSlug: async () => `slug-${++minted}`,
|
|
71
|
-
byId: async (id: string) => ({ id, slug: `slug-of-${id}` }),
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const details = (type: string, sub: string) => ({
|
|
78
|
-
type, service: type, clientId: type, userId: sub, username: 'person@example.org',
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const linkingFor = (ctx: unknown) => {
|
|
82
|
-
const service = makeIdentityLinkingService()
|
|
83
|
-
service.registerContext(ctx as never)
|
|
84
|
-
return service
|
|
85
|
-
}
|
|
86
|
-
|
|
87
18
|
describe('linkProfile', () => {
|
|
88
19
|
test('a first sign-in registers an organization, an account and a profile', async () => {
|
|
89
20
|
const ctx = makeCtx()
|
package/tsconfig.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": ["@owlmeans/dep-config/tsconfig.base.json", "@owlmeans/dep-config/tsconfig.
|
|
2
|
+
"extends": ["@owlmeans/dep-config/tsconfig.base.json", "@owlmeans/dep-config/tsconfig.node.json"],
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"rootDir": "./src",
|
|
5
5
|
"outDir": "./build/"
|