@owlmeans/server-auth-identity 0.1.18-rc.11 → 0.1.18-rc.13
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-auth-identity/SKILL.md +165 -80
- package/build/helper.d.ts.map +1 -1
- package/build/resolver.d.ts.map +1 -1
- package/build/resolver.js.map +1 -1
- package/build/resource.d.ts.map +1 -1
- package/build/resource.js +10 -0
- package/build/resource.js.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +67 -0
- package/build/service.js.map +1 -1
- package/build/types.d.ts +11 -0
- package/build/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/resource.ts +10 -0
- package/src/service.ts +74 -1
- package/src/types.ts +11 -0
- package/tests/linking.spec.ts +202 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Mongo-backed local identity resources and provider account linking for OwlMeans
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
bun add @owlmeans/server-auth-identity
|
|
15
|
+
bun add @owlmeans/server-auth-identity@^0.1.18-rc.12
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -102,7 +102,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
102
102
|
your project's skill store (`.agents/skills/`):
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
|
-
npx @owlmeans/agent-skills
|
|
105
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.12
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
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.13",
|
|
5
|
+
"generatedAt": "2026-09-04T22:43:25.434Z",
|
|
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 — Mongo-backed 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, and the EntityResolverService that turns the slug on a token into a stable entity id. Auto-invoked when importing appendAuthIdentityResources, an identity resource alias, IdentityLinkingService or the entity resolver.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,61 +8,108 @@ 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.13"` in `dependencies`
|
|
12
|
+
|
|
13
|
+
The local identity store a deployment owns when it does not delegate identity to an external IAM.
|
|
14
|
+
It answers two questions: *who is this person here* (account / profile / credentials) and *which
|
|
15
|
+
organization entity is this* (the org-entity registry and its resolver).
|
|
12
16
|
|
|
13
17
|
## Key Exports
|
|
14
18
|
|
|
15
19
|
| Export | Description |
|
|
16
20
|
|--------|-------------|
|
|
17
|
-
| `appendAuthIdentityResources(context, dbAlias?)` | Register
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
21
|
+
| `appendAuthIdentityResources(context, dbAlias?)` | Register the four resources, the linking service and the entity resolver in one call |
|
|
22
|
+
| `makeOrgEntityResource(dbAlias?)` | Mongo resource for `OrgEntity` — the organization-entity registry |
|
|
23
|
+
| `makeIdentityAccountResource(dbAlias?)` | Mongo resource for `IdentityAccount` (one per person) |
|
|
24
|
+
| `makeIdentityProfileResource(dbAlias?)` | Mongo resource for `IdentityProfile` (one person inside one organization entity, with a role) |
|
|
25
|
+
| `makeIdentityCredentialsResource(dbAlias?)` | Mongo resource for `IdentityCredentials` (one provider link per profile) |
|
|
26
|
+
| `makeIdentityLinkingService()` | The `IdentityLinkingService` implementation |
|
|
27
|
+
| `makeEntityResolverService(alias?)` | The `EntityResolverService` implementation, cached for 30 s per resolved value |
|
|
28
|
+
| `AUTH_IDENTITY_ORG_ENTITY` | Resource alias `'auth-identity:org-entity'` |
|
|
22
29
|
| `AUTH_IDENTITY_ACCOUNT` | Resource alias `'auth-identity:account'` |
|
|
23
30
|
| `AUTH_IDENTITY_PROFILE` | Resource alias `'auth-identity:profile'` |
|
|
24
31
|
| `AUTH_IDENTITY_CREDENTIALS` | Resource alias `'auth-identity:credentials'` |
|
|
25
32
|
| `AUTH_IDENTITY_LINKING` | Service alias `'auth-identity:linking'` |
|
|
33
|
+
| `AUTH_IDENTITY_DB_ALIAS` | `'auth-identity'` — the suggested db-config alias whose `resourcePrefix` scopes the prefix to identity collections only |
|
|
34
|
+
| `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
|
+
| `MAX_ENTITY_SLUG_ATTEMPTS` | How many word slugs to try before giving up minting a free one |
|
|
36
|
+
| `LOGIN_SERVICE_PREFIX`, `EXTERNAL_KEY_DELIMITER` | `'service'` and `':'` — the grammar of every derived key below |
|
|
37
|
+
|
|
38
|
+
The resource **aliases** keep their colon-delimited form and are lookup keys only; the Mongo
|
|
39
|
+
collection names are the colon-free `*_COLLECTION` values, optionally prefixed by the db config's
|
|
40
|
+
`resourcePrefix`.
|
|
26
41
|
|
|
27
42
|
## Types
|
|
28
43
|
|
|
29
44
|
| Type | Description |
|
|
30
45
|
|------|-------------|
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
46
|
+
| `OrgEntity` | `id`, `slug`, `formerSlugs?`, `iamKey`, `names?`, `createdAt`, `updatedAt?` |
|
|
47
|
+
| `IdentityAccount` | `Profile` without `entitySlug`, plus `id`, `credential`, `entityId?` |
|
|
48
|
+
| `IdentityProfile` | `Profile` without `entitySlug`, plus `id`, `profileId`, `userId?`, `role`, `entityId?`, `expiresAt?` |
|
|
49
|
+
| `IdentityCredentials` | `AuthCredentials` + `profileId` |
|
|
34
50
|
| `IdentityLinkingService` | `getLinkedProfile`, `linkProfile`, `linkCredentials`, `getOwnerProfiles`, `getOwnerCredentials` |
|
|
51
|
+
| `AccountMeta` | `{ username, force? }` — the second argument to `linkProfile` |
|
|
52
|
+
| `OrgEntityResource`, `IdentityAccountResource`, `IdentityProfileResource`, `IdentityCredentialsResource` | Typed `MongoResource` aliases |
|
|
53
|
+
| `IdentityConfig`, `IdentityContext` | Server config/context shapes |
|
|
54
|
+
| `GoogleUserInfo` | The Google userinfo claim set |
|
|
55
|
+
| `EmailIdentityArgs`, `EmailIdentityPayload`, `IdentityIamExtension` | Declaration-only seam for an IAM-backed email find-or-create |
|
|
56
|
+
|
|
57
|
+
The three `*Identity*` seam types are declarations and nothing more: no function in this
|
|
58
|
+
package accepts or returns them, and no consumer imports them. An email login is wired through
|
|
59
|
+
`IdentityLinkingService.getLinkedProfile` / `linkProfile`, which is what the OTP plugin does.
|
|
60
|
+
|
|
61
|
+
## The organization entity
|
|
62
|
+
|
|
63
|
+
The customer organization is an **organization entity**, and this package is the registry that owns
|
|
64
|
+
it. Two values name one organization and they never substitute for each other:
|
|
65
|
+
|
|
66
|
+
- **`entityId`** — `OrgEntity.id`. Stable, never on the wire. Every account, profile and
|
|
67
|
+
organization-scoped record keys on it.
|
|
68
|
+
- **`entitySlug`** — `OrgEntity.slug`. Renameable, human-readable, and the only organization value
|
|
69
|
+
an auth payload carries. Every `AuthPayload` this service returns carries `entitySlug`, resolved
|
|
70
|
+
from the stored `entityId`; an id that no longer resolves yields `undefined` rather than leaking a
|
|
71
|
+
raw id where a consumer would read it as a slug.
|
|
72
|
+
|
|
73
|
+
A rename is one write to `OrgEntity`, because nothing else stores the slug. The retired name moves
|
|
74
|
+
into `formerSlugs` and keeps resolving, so tokens minted before the rename and third-party records
|
|
75
|
+
still quoting the old value keep working.
|
|
76
|
+
|
|
77
|
+
`iamKey` is frozen at creation and never recomputed. It is what systems that cannot be renamed —
|
|
78
|
+
an IAM realm, a namespace, a storage prefix — are told once; `mintName(id, key, mint)` is the
|
|
79
|
+
read-before-mint that freezes such a derived name into `OrgEntity.names`.
|
|
80
|
+
|
|
81
|
+
Registering the resolver is also the signal that this deployment **has** organizations: without it
|
|
82
|
+
`request.entity` stays undefined and every consumer falls back to treating the token's slug as the
|
|
83
|
+
only entity value there is.
|
|
35
84
|
|
|
36
85
|
## Usage
|
|
37
86
|
|
|
38
|
-
### Register in context
|
|
87
|
+
### Register in a context
|
|
39
88
|
|
|
40
89
|
```typescript
|
|
41
90
|
import { appendAuthIdentityResources } from '@owlmeans/server-auth-identity'
|
|
42
91
|
|
|
43
|
-
//
|
|
92
|
+
// in makeContext, after the Mongo/Redis services and appendAuthService:
|
|
44
93
|
appendAuthIdentityResources(context)
|
|
45
94
|
```
|
|
46
95
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### Read a profile (gate / handler)
|
|
96
|
+
### Read a profile (gate or handler)
|
|
50
97
|
|
|
51
98
|
```typescript
|
|
52
99
|
import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity'
|
|
53
100
|
import type { IdentityProfileResource } from '@owlmeans/server-auth-identity'
|
|
101
|
+
import { requireEntityKey } from '@owlmeans/auth-common'
|
|
54
102
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
// One record by any field, or by several at once — read-only, and typed by the resource:
|
|
58
|
-
const profile = await profileRes.load({ profileId })
|
|
59
|
-
const scoped = await profileRes.load({ entityId, profileId })
|
|
103
|
+
const profiles = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
104
|
+
const entityId = requireEntityKey(req)
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
const { items
|
|
106
|
+
const profile = await profiles.load({ entityId, profileId })
|
|
107
|
+
const { items } = await profiles.list({ entityId }, { sort: [{ field: 'createdAt', order: 'desc' }] })
|
|
63
108
|
```
|
|
64
109
|
|
|
65
|
-
|
|
110
|
+
Query profiles by `entityId`, never by the slug — `requireEntityKey` hands you the resolved id.
|
|
111
|
+
|
|
112
|
+
### Link a provider account to a local identity
|
|
66
113
|
|
|
67
114
|
```typescript
|
|
68
115
|
import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
|
|
@@ -70,47 +117,75 @@ import type { IdentityLinkingService } from '@owlmeans/server-auth-identity'
|
|
|
70
117
|
|
|
71
118
|
const linking = ctx.service<IdentityLinkingService>(AUTH_IDENTITY_LINKING)
|
|
72
119
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Both return `AuthPayload` with `{ type, role, userId, profileId, entityId, scopes }`.
|
|
81
|
-
|
|
82
|
-
### ProviderProfileDetails shape (input)
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
interface ProviderProfileDetails {
|
|
86
|
-
type: string // e.g. 'google'
|
|
87
|
-
service: string // e.g. 'google' (provider service alias)
|
|
88
|
-
userId: string // provider subject (sub claim)
|
|
89
|
-
profileId?: string // optional existing profile to link to
|
|
120
|
+
const details = {
|
|
121
|
+
type: 'google-oauth', // the AuthenticationType
|
|
122
|
+
service: 'google', // the provider service alias
|
|
123
|
+
clientId: 'google', // the provider client this login came through
|
|
124
|
+
userId: providerSub, // the provider's subject claim
|
|
90
125
|
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Key Derivation Conventions
|
|
94
|
-
|
|
95
|
-
- **Profile userId**: the account's mongo id — a **declared ObjectId reference**
|
|
96
|
-
(`resource.reference('userId', AUTH_IDENTITY_ACCOUNT)`): stored as `ObjectId`, exchanged
|
|
97
|
-
as a string, auto-indexed and auto-migrated at boot. The only reference in the trio.
|
|
98
|
-
- **Account credential**: unique Base58 slug (16 chars) — serves as the local `entityId`
|
|
99
|
-
- **Profile profileId**: `"{type}:{accountId}"` — stable across provider re-links; a composite key, NOT a reference
|
|
100
|
-
- **Credentials userId** (external key): `"{type}:{service}:{providerSub}"` — uniquely identifies the external account. Same name as `profile.userId`, entirely different meaning — never convert or compare across the two
|
|
101
|
-
- **Credentials credential** (login-service key): `"service:{type}:{service}"` — groups credentials by provider
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
by the
|
|
127
|
+
// Returning login: find the credential, then its profile.
|
|
128
|
+
let payload = await linking.getLinkedProfile(details)
|
|
129
|
+
// First login by this method: link it to the person's identity, registering one if new.
|
|
130
|
+
payload ??= await linking.linkProfile(details, { username: 'person@example.org' })
|
|
131
|
+
```
|
|
106
132
|
|
|
107
|
-
|
|
133
|
+
Both return an `AuthPayload` — `{ type, role, userId, profileId, entitySlug, scopes }`.
|
|
134
|
+
|
|
135
|
+
`linkCredentials(details)` adds a credential row to an already-linked profile named by
|
|
136
|
+
`details.profileId`. `getOwnerProfiles(entityId)` lists an organization's profiles;
|
|
137
|
+
`getOwnerCredentials(userId, entityId?, type?)` returns one profile's credential as an
|
|
138
|
+
`AuthCredentials`.
|
|
139
|
+
|
|
140
|
+
## One person, one platform identity
|
|
141
|
+
|
|
142
|
+
`linkProfile` **links** a person the platform already knows and registers only a genuinely new one.
|
|
143
|
+
It matches on the account's `name`, which every shipped provider establishes as a verified email
|
|
144
|
+
before it gets here (Google asserts the address, OTP proves possession of it, the supervisor key is
|
|
145
|
+
a full-trust developer credential). A second sign-in method therefore adds a second **credential**
|
|
146
|
+
to one profile — never a second account, profile or organization entity.
|
|
147
|
+
|
|
148
|
+
Two rows can legitimately carry one address, and only one of them is a platform login: an
|
|
149
|
+
organization's invited **end user** is a person-shaped row for the generated application to
|
|
150
|
+
authenticate, deliberately separate from the platform credential. They are told apart by the
|
|
151
|
+
profile's `credential`: this service is the only writer of `"service:{type}:{service}"`, so a
|
|
152
|
+
profile carrying one is a platform login and a profile carrying none is somebody's end user.
|
|
153
|
+
|
|
154
|
+
`linkProfile(details, { username, force: true })` skips the lookup and registers a fresh identity
|
|
155
|
+
regardless. Use it only where a genuinely separate identity is intended.
|
|
156
|
+
|
|
157
|
+
## Key derivation conventions
|
|
158
|
+
|
|
159
|
+
- **Account `credential`** — a unique 16-character Base58 slug, the account's own stable key.
|
|
160
|
+
Generation retries up to five times on a duplicate-key collision; that is the designed behaviour
|
|
161
|
+
for the slug space, not an error path.
|
|
162
|
+
- **Account / profile `entityId`** — the `OrgEntity` record id. Every first registration creates the
|
|
163
|
+
entity record **first**, because it owns the id everything else is keyed by and the frozen
|
|
164
|
+
`iamKey`.
|
|
165
|
+
- **Profile `userId`** — the account's Mongo id, and a **declared ObjectId reference**
|
|
166
|
+
(`resource.reference('userId', AUTH_IDENTITY_ACCOUNT)`): stored as `ObjectId`, exchanged as a
|
|
167
|
+
string, auto-indexed and auto-migrated at boot. The only reference in the set.
|
|
168
|
+
- **Profile `profileId`** — `"{type}:{accountId}"`. A composite key, stable across provider
|
|
169
|
+
re-links, and NOT a reference.
|
|
170
|
+
- **Credentials `userId`** (external key) — `"{type}:{service}:{providerSub}"`. Same field name as
|
|
171
|
+
`profile.userId`, entirely different meaning; never compare or convert across the two.
|
|
172
|
+
- **Credentials / profile `credential`** (login-service key) — `"service:{type}:{service}"`.
|
|
173
|
+
|
|
174
|
+
`AuthPayload.userId` is emitted as `profile.userId ?? profile.profileId`, so a value coming back
|
|
175
|
+
into a `userId` query may be a composite key. The reference conversion tolerates it by matching
|
|
176
|
+
nothing.
|
|
177
|
+
|
|
178
|
+
## Resource indexes
|
|
108
179
|
|
|
109
180
|
| Resource | Index | Fields | Notes |
|
|
110
181
|
|----------|-------|--------|-------|
|
|
182
|
+
| OrgEntity | `slug` | `{ slug: 1 }` | unique — the only thing between two organizations and one public name |
|
|
183
|
+
| OrgEntity | `iamKey` | `{ iamKey: 1 }` | unique — a duplicate would hand one organization another's realm |
|
|
184
|
+
| OrgEntity | `formerSlugs` | `{ formerSlugs: 1 }` | not unique; a retired slug must stay findable |
|
|
111
185
|
| Account | `credential` | `{ credential: 1 }` | unique |
|
|
112
186
|
| Account | `entityId` | `{ entityId: 1 }` | |
|
|
113
187
|
| Account | `secret` | `{ secret: 1 }` | unique, sparse |
|
|
188
|
+
| Account | `name` | `{ name: 1 }` | not unique — one address is legitimately both a platform account and an end-user row |
|
|
114
189
|
| Profile | `userId` | `{ userId: 1 }` | |
|
|
115
190
|
| Profile | `entityId` | `{ entityId: 1 }` | |
|
|
116
191
|
| Profile | `role` | `{ role: 1, entityId: 1 }` | |
|
|
@@ -118,32 +193,42 @@ by the reference conversion's design.
|
|
|
118
193
|
| Credentials | `provider` | `{ type: 1, userId: 1, credential: 1 }` | unique |
|
|
119
194
|
| Credentials | `profileId` | `{ profileId: 1 }` | |
|
|
120
195
|
|
|
121
|
-
##
|
|
122
|
-
|
|
123
|
-
- **`Resource.take()` is destructive** — it deletes the record it returns.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
196
|
+
## Gotchas
|
|
197
|
+
|
|
198
|
+
- **`Resource.take()` is destructive** — it deletes the record it returns. Identity lookups in gates
|
|
199
|
+
and handlers use `load(where)`, which answers a multi-field query in one call
|
|
200
|
+
(`load({ type, userId, credential })`), or `list(where, opts)` for every match. Never `take()` in
|
|
201
|
+
an authorization check.
|
|
202
|
+
- **`IdentityLinkingService` is compatible with `AccountLinkingService`** from
|
|
203
|
+
`@owlmeans/server-oidc-rp` but declared independently, to keep the dependency acyclic. Both return
|
|
204
|
+
`AuthPayload`.
|
|
205
|
+
- First-login profiles are created with `ALL_SCOPES` and `AuthRole.User` — the gate layer starts
|
|
206
|
+
permissive within one organization entity. Narrow the scopes where finer authorization is needed.
|
|
207
|
+
- `mintSlug` throws `SyntaxError('entity:slug-exhausted')` after `MAX_ENTITY_SLUG_ATTEMPTS` word
|
|
208
|
+
bases (three numeric suffixes each). `rename` throws `entity:slug-malformed:<slug>` against
|
|
209
|
+
`ENTITY_SLUG_PATTERN` and `entity:slug-taken:<slug>` for a name any entity has ever answered to.
|
|
210
|
+
- The resolver caches every name a hit was found under — id, slug, former slug, `iamKey` — for 30
|
|
211
|
+
seconds. A rename is therefore visible to other replicas within that window, which is survivable
|
|
212
|
+
precisely because the old slug keeps resolving.
|
|
213
|
+
|
|
214
|
+
## Relationship to other auth packages
|
|
215
|
+
|
|
216
|
+
- **`@owlmeans/server-auth`** — verifies the bearer token and canonicalizes the slug on the
|
|
217
|
+
credential through this package's resolver. This package is the store underneath.
|
|
218
|
+
- **`@owlmeans/auth-common`** — declares `EntityResolverService` (implemented here),
|
|
219
|
+
`ENTITY_RESOLVER` and `OrgEntityRef`, and carries the `entityKeyOf` / `requireEntityKey` /
|
|
220
|
+
`attachEntity` helpers that read what this package resolves.
|
|
221
|
+
- **`@owlmeans/server-oidc-rp`** — the relying party that calls the linking service during an OAuth
|
|
222
|
+
callback.
|
|
223
|
+
- **`@owlmeans/server-auth-otp`** — the email-OTP plugin resolves its user through the linking
|
|
224
|
+
service.
|
|
225
|
+
- **`@owlmeans/oidc`** — supplies `ProviderProfileDetails` and `OidcProviderDescriptor`.
|
|
140
226
|
|
|
141
227
|
## Depends On
|
|
142
228
|
|
|
143
229
|
- `@owlmeans/auth` — `AuthPayload`, `AuthRole`, `ALL_SCOPES`, `Profile`, `AuthCredentials`
|
|
144
|
-
- `@owlmeans/
|
|
145
|
-
- `@owlmeans/
|
|
146
|
-
- `@owlmeans/mongo-resource` — `makeMongoResource
|
|
147
|
-
- `@owlmeans/oidc
|
|
148
|
-
-
|
|
149
|
-
- `@owlmeans/server-context` — `ServerConfig`, `ServerContext`
|
|
230
|
+
- `@owlmeans/auth-common` — `EntityResolverService`, `ENTITY_RESOLVER`, `ENTITY_SLUG_PATTERN`
|
|
231
|
+
- `@owlmeans/basic-ids` — `createIdOfLength`, `IdStyle`, `generateWordSlug`, `nextSlugCandidate`
|
|
232
|
+
- `@owlmeans/mongo-resource` — `makeMongoResource`, declared references and indexes
|
|
233
|
+
- `@owlmeans/context`, `@owlmeans/resource`, `@owlmeans/oidc`, `@owlmeans/server-context`
|
|
234
|
+
- `mongodb` (peer) — the driver behind the Mongo resources this package registers
|
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;AAKrD,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,2BAA2B,YAC7B,YAAY,CAAC,GAAG,CAAC,YAChB,MAAM,KACf,IAUF,CAAA"}
|
package/build/resolver.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAgB,MAAM,uBAAuB,CAAA;AA6BhF,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAgB,MAAM,uBAAuB,CAAA;AA6BhF,eAAO,MAAM,yBAAyB,WAC7B,MAAM,KACZ,qBAkIF,CAAA"}
|
package/build/resolver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAIpD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEzE,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAIhF;;;;;;;GAOG;AACH,MAAM,SAAS,GAAG,MAAM,CAAA;AAExB,wFAAwF;AACxF,MAAM,KAAK,GAAG,CAAC,MAAwB,EAAuB,EAAE,CAC9D,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC,EAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM;CAC1F,CAAA;AAOH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"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"}
|
package/build/resource.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,uBAAuB,EAAmB,uBAAuB,EAAuB,2BAA2B,EAAa,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAOpM;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,uBAAuB,EAAmB,uBAAuB,EAAuB,2BAA2B,EAAa,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAOpM;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,aAAc,MAAM,KAAG,iBAQxD,CAAA;AAED,eAAO,MAAM,2BAA2B,aAAc,MAAM,KAAG,uBAkB9D,CAAA;AAED,eAAO,MAAM,2BAA2B,aAAc,MAAM,KAAG,uBAe9D,CAAA;AAED,eAAO,MAAM,+BAA+B,aAAc,MAAM,KAAG,2BAOlE,CAAA"}
|
package/build/resource.js
CHANGED
|
@@ -21,6 +21,16 @@ export const makeIdentityAccountResource = (dbAlias) => {
|
|
|
21
21
|
resource.index('credential', { credential: 1 }, { unique: true });
|
|
22
22
|
resource.index('entityId', { entityId: 1 });
|
|
23
23
|
resource.index('secret', { secret: 1 }, { unique: true, sparse: true });
|
|
24
|
+
/**
|
|
25
|
+
* The account's login name — an email, for every provider shipped today.
|
|
26
|
+
*
|
|
27
|
+
* Indexed because `linkProfile` reads it on every registration: a person signing in by a second
|
|
28
|
+
* method must land on the identity they already have, not a new one. NOT unique — the same
|
|
29
|
+
* address legitimately appears twice, once for the platform account and once for an
|
|
30
|
+
* organization's END USER record (`inviteUser`), and those are different people-shaped rows for
|
|
31
|
+
* different audiences. The two are told apart by the profile, never by the account.
|
|
32
|
+
*/
|
|
33
|
+
resource.index('name', { name: 1 });
|
|
24
34
|
return resource;
|
|
25
35
|
};
|
|
26
36
|
export const makeIdentityProfileResource = (dbAlias) => {
|
package/build/resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
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;AAmBpJ,eAAO,MAAM,0BAA0B,QAAO,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAmBpJ,eAAO,MAAM,0BAA0B,QAAO,sBA8Q7C,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -23,6 +23,40 @@ export const makeIdentityLinkingService = () => {
|
|
|
23
23
|
const entity = await ctx.service(ENTITY_RESOLVER).byId(entityId);
|
|
24
24
|
return entity?.slug;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The platform identity this person already has, if any.
|
|
28
|
+
*
|
|
29
|
+
* One human, one email, one profile — whichever way they sign in. Every provider that reaches
|
|
30
|
+
* `linkProfile` establishes the email first (Google asserts a verified address, the OTP flow
|
|
31
|
+
* proves possession of it, the supervisor key is a full-trust developer credential), so the
|
|
32
|
+
* name is the identity and a second method is a second CREDENTIAL on it, never a second person.
|
|
33
|
+
* Without this the platform minted a whole new organization per method: the same address signed
|
|
34
|
+
* in by Google and by key ended up in two entities, and each could see only the projects the
|
|
35
|
+
* other had not created.
|
|
36
|
+
*
|
|
37
|
+
* Two rows can carry one address, and only one of them is a platform login. `inviteUser`
|
|
38
|
+
* (`@owlmeans/iam-integrated`) writes an END USER record for the same person — the identity the
|
|
39
|
+
* GENERATED application authenticates, deliberately kept apart from the platform credential.
|
|
40
|
+
* Those rows carry no login service, which is what tells the two apart here: this service is
|
|
41
|
+
* the only writer of `credential = "service:{type}:{service}"`, so a profile that has one is a
|
|
42
|
+
* platform login and a profile that has none is somebody's end user.
|
|
43
|
+
*/
|
|
44
|
+
const findPlatformIdentity = async (username) => {
|
|
45
|
+
const name = username.trim();
|
|
46
|
+
if (name === '')
|
|
47
|
+
return null;
|
|
48
|
+
const ctx = service.ctx;
|
|
49
|
+
const accountResource = ctx.resource(AUTH_IDENTITY_ACCOUNT);
|
|
50
|
+
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
|
51
|
+
const { items: accounts } = await accountResource.list({ name });
|
|
52
|
+
for (const account of accounts) {
|
|
53
|
+
const { items: profiles } = await profileResource.list({ userId: account.id });
|
|
54
|
+
const platform = profiles.find(profile => profile.credential?.startsWith(`${LOGIN_SERVICE_PREFIX}${EXTERNAL_KEY_DELIMITER}`) === true);
|
|
55
|
+
if (platform != null)
|
|
56
|
+
return platform;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
26
60
|
const service = appendContextual(AUTH_IDENTITY_LINKING, {
|
|
27
61
|
getLinkedProfile: async (details) => {
|
|
28
62
|
const ctx = service.ctx;
|
|
@@ -54,6 +88,27 @@ export const makeIdentityLinkingService = () => {
|
|
|
54
88
|
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
55
89
|
const entityResource = ctx.resource(AUTH_IDENTITY_ORG_ENTITY);
|
|
56
90
|
const resolver = ctx.service(ENTITY_RESOLVER);
|
|
91
|
+
// A person the platform already knows is LINKED, not registered again. Everything below
|
|
92
|
+
// this point creates an organization, an account and a profile, and doing that for a second
|
|
93
|
+
// login method is what put one email in two entities.
|
|
94
|
+
const known = meta.force === true ? null : await findPlatformIdentity(meta.username);
|
|
95
|
+
if (known != null) {
|
|
96
|
+
await credsResource.create({
|
|
97
|
+
challenge: '',
|
|
98
|
+
type: details.type,
|
|
99
|
+
userId: externalKey(details),
|
|
100
|
+
profileId: known.profileId,
|
|
101
|
+
credential: loginService(details),
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
type: details.type,
|
|
105
|
+
role: known.role,
|
|
106
|
+
userId: known.userId ?? known.profileId,
|
|
107
|
+
profileId: known.profileId,
|
|
108
|
+
entitySlug: await slugOf(known.entityId),
|
|
109
|
+
scopes: known.scopes,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
57
112
|
// Every account gets its own organization. The entity record is created FIRST because it
|
|
58
113
|
// owns the two values that outlive everything else here: the id the account and its profiles
|
|
59
114
|
// are keyed by, and the frozen `iamKey` that realms and object names are minted from.
|
|
@@ -144,6 +199,18 @@ export const makeIdentityLinkingService = () => {
|
|
|
144
199
|
}
|
|
145
200
|
return result;
|
|
146
201
|
},
|
|
202
|
+
unlinkCredentials: async (details) => {
|
|
203
|
+
const ctx = service.ctx;
|
|
204
|
+
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
205
|
+
const cred = await credsResource.load({
|
|
206
|
+
type: details.type,
|
|
207
|
+
userId: externalKey(details),
|
|
208
|
+
credential: loginService(details),
|
|
209
|
+
});
|
|
210
|
+
if (cred?.id == null)
|
|
211
|
+
return;
|
|
212
|
+
await credsResource.delete(cred.id);
|
|
213
|
+
},
|
|
147
214
|
getOwnerProfiles: async (entityId) => {
|
|
148
215
|
const ctx = service.ctx;
|
|
149
216
|
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
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;AAIpM,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAE1B,8DAA8D;AAC9D,MAAM,WAAW,GAAG,CAAC,OAA+B,EAAU,EAAE,CAC9D,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;AAE9E,yFAAyF;AACzF,MAAM,YAAY,GAAG,CAAC,OAA+B,EAAU,EAAE,CAC/D,CAAC,oBAAoB,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAA2B,EAAE;IACrE;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,KAAK,EAAE,QAAiB,EAA+B,EAAE;QACtE,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,EAAE;YAAE,OAAO,SAAS,CAAA;QACzD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAwB,eAAe,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEvF,OAAO,MAAM,EAAE,IAAI,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,OAAO,GAA2B,gBAAgB,CAAyB,qBAAqB,EAAE;QACtG,gBAAgB,EAAE,KAAK,EAAE,OAA+B,EAA+B,EAAE;YACvF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAE1F,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC;gBACpC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC5B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;aAClC,CAAC,CAAA;YACF,IAAI,IAAI,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;YAE7B,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;YACzE,IAAI,OAAO,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;YAEhC,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAA;QACH,CAAC;QAED,WAAW,EAAE,KAAK,EAAE,OAA+B,EAAE,IAAiB,EAAwB,EAAE;YAC9F,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAC1F,MAAM,cAAc,GAAG,GAAG,CAAC,QAAQ,CAAoB,wBAAwB,CAAC,CAAA;YAChF,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAwB,eAAe,CAAC,CAAA;YAEpE,yFAAyF;YACzF,6FAA6F;YAC7F,sFAAsF;YACtF,MAAM,MAAM,GAAc,MAAM,cAAc,CAAC,MAAM,CAAC;gBACpD,IAAI,EAAE,MAAM,QAAQ,CAAC,QAAQ,EAAE;gBAC/B,WAAW,EAAE,EAAE;gBACf,wFAAwF;gBACxF,2EAA2E;gBAC3E,MAAM,EAAE,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC;gBAC5C,KAAK,EAAE,EAAE;gBACT,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB,CAAC,CAAA;YACF,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAG,CAAA;YAE3B,IAAI,SAAS,GAAkB,IAAI,CAAA;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC1C,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;gBACvD,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;wBAC3C,UAAU;wBACV,IAAI,EAAE,IAAI,CAAC,QAAQ;wBACnB,QAAQ;qBACT,CAAC,CAAA;oBACF,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;oBACtB,MAAK;gBACP,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,IAAI,GAAG,EAAE,IAAI,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC;wBAAE,SAAQ;oBACxE,MAAM,GAAG,CAAA;gBACX,CAAC;YACH,CAAC;YAED,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAA;YAC/E,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;YACxE,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;YAEjC,IAAI,OAAO,CAAA;YACX,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;oBACrC,SAAS;oBACT,MAAM,EAAE,SAAS;oBACjB,UAAU,EAAE,GAAG;oBACf,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,IAAI,EAAE,IAAI,CAAC,QAAQ;oBACnB,QAAQ;oBACR,MAAM,EAAE,CAAC,UAAU,CAAC;iBACrB,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,IAAI,GAAG,EAAE,IAAI,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC/D,OAAO,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;gBACpD,CAAC;qBAAM,CAAC;oBACN,MAAM,GAAG,CAAA;gBACX,CAAC;YACH,CAAC;YAED,MAAM,aAAa,CAAC,MAAM,CAAC;gBACzB,SAAS,EAAE,EAAE;gBACb,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC5B,SAAS;gBACT,UAAU,EAAE,GAAG;aAChB,CAAC,CAAA;YAEF,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAA;QACH,CAAC;QAED,eAAe,EAAE,KAAK,EAAE,OAA+B,EAAwB,EAAE;YAC/E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;YACtD,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;YAC/D,CAAC;YAED,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;gBAClC,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;gBAC1F,MAAM,aAAa,CAAC,MAAM,CAAC;oBACzB,SAAS,EAAE,EAAE;oBACb,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;oBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;iBAClC,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,MAAM,CAAA;QACf,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,QAAgB,EAAsB,EAAE;YAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YAEpE,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,EAAC,EAAE,CAAC,CAAC;gBAChD,EAAE,EAAE,CAAC,CAAC,SAAS;gBACf,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,UAAU,EAAE,MAAM,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACpC,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC,CAAC,CAAC,CAAA;QACN,CAAC;QAED,mBAAmB,EAAE,KAAK,EAAE,MAAc,EAAE,QAAiB,EAAE,IAAa,EAAwC,EAAE;YACpH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAE1F,MAAM,aAAa,GAA8B,EAAE,MAAM,EAAE,CAAA;YAC3D,IAAI,QAAQ,IAAI,IAAI;gBAAE,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAA;YACvD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YACrE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAA;YAE3C,MAAM,WAAW,GAAkC;gBACjD,SAAS,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;aACnD,CAAA;YACD,IAAI,IAAI,IAAI,IAAI;gBAAE,WAAW,CAAC,IAAI,GAAG,IAAI,CAAA;YACzC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAClD,IAAI,IAAI,IAAI,IAAI;gBAAE,OAAO,SAAS,CAAA;YAElC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAA;YAEjF,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAA;QACH,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
|
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;AAIpM,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAE1B,8DAA8D;AAC9D,MAAM,WAAW,GAAG,CAAC,OAA+B,EAAU,EAAE,CAC9D,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;AAE9E,yFAAyF;AACzF,MAAM,YAAY,GAAG,CAAC,OAA+B,EAAU,EAAE,CAC/D,CAAC,oBAAoB,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;AAEpF,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAA2B,EAAE;IACrE;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,KAAK,EAAE,QAAiB,EAA+B,EAAE;QACtE,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,EAAE;YAAE,OAAO,SAAS,CAAA;QACzD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAwB,eAAe,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEvF,OAAO,MAAM,EAAE,IAAI,CAAA;IACrB,CAAC,CAAA;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,oBAAoB,GAAG,KAAK,EAAE,QAAgB,EAAmC,EAAE;QACvF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;QAC5B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,IAAI,CAAA;QAE5B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;QAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;QACpF,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;QAEpF,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAA+B,CAAC,CAAA;QAC7F,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAA+B,CAAC,CAAA;YAC3G,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CACvC,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,oBAAoB,GAAG,sBAAsB,EAAE,CAAC,KAAK,IAAI,CAC5F,CAAA;YACD,IAAI,QAAQ,IAAI,IAAI;gBAAE,OAAO,QAAQ,CAAA;QACvC,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,OAAO,GAA2B,gBAAgB,CAAyB,qBAAqB,EAAE;QACtG,gBAAgB,EAAE,KAAK,EAAE,OAA+B,EAA+B,EAAE;YACvF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAE1F,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC;gBACpC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC5B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;aAClC,CAAC,CAAA;YACF,IAAI,IAAI,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;YAE7B,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;YACzE,IAAI,OAAO,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;YAEhC,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAA;QACH,CAAC;QAED,WAAW,EAAE,KAAK,EAAE,OAA+B,EAAE,IAAiB,EAAwB,EAAE;YAC9F,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAC1F,MAAM,cAAc,GAAG,GAAG,CAAC,QAAQ,CAAoB,wBAAwB,CAAC,CAAA;YAChF,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAwB,eAAe,CAAC,CAAA;YAEpE,wFAAwF;YACxF,4FAA4F;YAC5F,sDAAsD;YACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACpF,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,MAAM,aAAa,CAAC,MAAM,CAAC;oBACzB,SAAS,EAAE,EAAE;oBACb,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;oBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;iBAClC,CAAC,CAAA;gBAEF,OAAO;oBACL,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,SAAS;oBACvC,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,UAAU,EAAE,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;oBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;iBACrB,CAAA;YACH,CAAC;YAED,yFAAyF;YACzF,6FAA6F;YAC7F,sFAAsF;YACtF,MAAM,MAAM,GAAc,MAAM,cAAc,CAAC,MAAM,CAAC;gBACpD,IAAI,EAAE,MAAM,QAAQ,CAAC,QAAQ,EAAE;gBAC/B,WAAW,EAAE,EAAE;gBACf,wFAAwF;gBACxF,2EAA2E;gBAC3E,MAAM,EAAE,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC;gBAC5C,KAAK,EAAE,EAAE;gBACT,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB,CAAC,CAAA;YACF,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAG,CAAA;YAE3B,IAAI,SAAS,GAAkB,IAAI,CAAA;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC1C,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;gBACvD,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;wBAC3C,UAAU;wBACV,IAAI,EAAE,IAAI,CAAC,QAAQ;wBACnB,QAAQ;qBACT,CAAC,CAAA;oBACF,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;oBACtB,MAAK;gBACP,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,IAAI,GAAG,EAAE,IAAI,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC;wBAAE,SAAQ;oBACxE,MAAM,GAAG,CAAA;gBACX,CAAC;YACH,CAAC;YAED,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAA;YAC/E,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;YACxE,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;YAEjC,IAAI,OAAO,CAAA;YACX,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;oBACrC,SAAS;oBACT,MAAM,EAAE,SAAS;oBACjB,UAAU,EAAE,GAAG;oBACf,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,IAAI,EAAE,IAAI,CAAC,QAAQ;oBACnB,QAAQ;oBACR,MAAM,EAAE,CAAC,UAAU,CAAC;iBACrB,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,IAAI,GAAG,EAAE,IAAI,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC/D,OAAO,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;gBACpD,CAAC;qBAAM,CAAC;oBACN,MAAM,GAAG,CAAA;gBACX,CAAC;YACH,CAAC;YAED,MAAM,aAAa,CAAC,MAAM,CAAC;gBACzB,SAAS,EAAE,EAAE;gBACb,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC5B,SAAS;gBACT,UAAU,EAAE,GAAG;aAChB,CAAC,CAAA;YAEF,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAA;QACH,CAAC;QAED,eAAe,EAAE,KAAK,EAAE,OAA+B,EAAwB,EAAE;YAC/E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;YACtD,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;YAC/D,CAAC;YAED,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;gBAClC,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;gBAC1F,MAAM,aAAa,CAAC,MAAM,CAAC;oBACzB,SAAS,EAAE,EAAE;oBACb,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;oBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;iBAClC,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,MAAM,CAAA;QACf,CAAC;QAED,iBAAiB,EAAE,KAAK,EAAE,OAA+B,EAAiB,EAAE;YAC1E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAC1F,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC;gBACpC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC5B,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC;aAClC,CAAC,CAAA;YACF,IAAI,IAAI,EAAE,EAAE,IAAI,IAAI;gBAAE,OAAM;YAC5B,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACrC,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,QAAgB,EAAsB,EAAE;YAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YAEpE,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,EAAC,EAAE,CAAC,CAAC;gBAChD,EAAE,EAAE,CAAC,CAAC,SAAS;gBACf,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,UAAU,EAAE,MAAM,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACpC,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC,CAAC,CAAC,CAAA;QACN,CAAC;QAED,mBAAmB,EAAE,KAAK,EAAE,MAAc,EAAE,QAAiB,EAAE,IAAa,EAAwC,EAAE;YACpH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAc,CAAA;YAClC,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAA0B,qBAAqB,CAAC,CAAA;YACpF,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAA8B,yBAAyB,CAAC,CAAA;YAE1F,MAAM,aAAa,GAA8B,EAAE,MAAM,EAAE,CAAA;YAC3D,IAAI,QAAQ,IAAI,IAAI;gBAAE,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAA;YACvD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YACrE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAA;YAE3C,MAAM,WAAW,GAAkC;gBACjD,SAAS,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;aACnD,CAAA;YACD,IAAI,IAAI,IAAI,IAAI;gBAAE,WAAW,CAAC,IAAI,GAAG,IAAI,CAAA;YACzC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAClD,IAAI,IAAI,IAAI,IAAI;gBAAE,OAAO,SAAS,CAAA;YAElC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAA;YAEjF,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS;gBAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAA;QACH,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
package/build/types.d.ts
CHANGED
|
@@ -70,6 +70,17 @@ export interface IdentityLinkingService extends InitializedService {
|
|
|
70
70
|
getLinkedProfile: (details: ProviderProfileDetails) => Promise<AuthPayload | null>;
|
|
71
71
|
linkProfile: (details: ProviderProfileDetails, meta: AccountMeta) => Promise<AuthPayload>;
|
|
72
72
|
linkCredentials: (details: ProviderProfileDetails) => Promise<AuthPayload>;
|
|
73
|
+
/**
|
|
74
|
+
* Forget which profile an external login maps to, so the next `linkProfile` establishes it anew.
|
|
75
|
+
*
|
|
76
|
+
* The stored mapping is keyed `{type, userId, credential}` under a unique index, so a caller
|
|
77
|
+
* that has decided the existing mapping is wrong cannot simply write over it — it has to be
|
|
78
|
+
* retired first. That decision belongs to whoever owns the login path (a decorator serving a
|
|
79
|
+
* different population than the platform's own customers, say); the key format belongs here.
|
|
80
|
+
*
|
|
81
|
+
* Idempotent: a login that maps to nothing is already in the state this promises.
|
|
82
|
+
*/
|
|
83
|
+
unlinkCredentials: (details: ProviderProfileDetails) => Promise<void>;
|
|
73
84
|
getOwnerProfiles: (entityId: string) => Promise<Profile[]>;
|
|
74
85
|
getOwnerCredentials: (userId: string, entityId?: string, type?: string) => Promise<AuthCredentials | undefined>;
|
|
75
86
|
}
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
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.13",
|
|
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.9",
|
|
26
|
+
"@owlmeans/auth-common": "^0.1.18-rc.13",
|
|
27
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.9",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.8",
|
|
29
|
+
"@owlmeans/mongo-resource": "^0.1.18-rc.12",
|
|
30
|
+
"@owlmeans/oidc": "^0.1.18-rc.13",
|
|
31
|
+
"@owlmeans/resource": "^0.1.18-rc.9",
|
|
32
|
+
"@owlmeans/server-context": "^0.1.18-rc.12"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"mongodb": "*"
|
package/src/resource.ts
CHANGED
|
@@ -32,6 +32,16 @@ export const makeIdentityAccountResource = (dbAlias?: string): IdentityAccountRe
|
|
|
32
32
|
resource.index('credential', { credential: 1 }, { unique: true })
|
|
33
33
|
resource.index('entityId', { entityId: 1 })
|
|
34
34
|
resource.index('secret', { secret: 1 }, { unique: true, sparse: true })
|
|
35
|
+
/**
|
|
36
|
+
* The account's login name — an email, for every provider shipped today.
|
|
37
|
+
*
|
|
38
|
+
* Indexed because `linkProfile` reads it on every registration: a person signing in by a second
|
|
39
|
+
* method must land on the identity they already have, not a new one. NOT unique — the same
|
|
40
|
+
* address legitimately appears twice, once for the platform account and once for an
|
|
41
|
+
* organization's END USER record (`inviteUser`), and those are different people-shaped rows for
|
|
42
|
+
* different audiences. The two are told apart by the profile, never by the account.
|
|
43
|
+
*/
|
|
44
|
+
resource.index('name', { name: 1 })
|
|
35
45
|
return resource
|
|
36
46
|
}
|
|
37
47
|
|
package/src/service.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ProviderProfileDetails } from '@owlmeans/oidc'
|
|
|
6
6
|
import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids'
|
|
7
7
|
import type { Criteria } from '@owlmeans/resource'
|
|
8
8
|
import type { AccountMeta, IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource, IdentityLinkingService } from './types.js'
|
|
9
|
-
import type { IdentityCredentials, IdentityProfile } from './types.js'
|
|
9
|
+
import type { IdentityAccount, IdentityCredentials, IdentityProfile } from './types.js'
|
|
10
10
|
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'
|
|
@@ -40,6 +40,44 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
|
|
|
40
40
|
return entity?.slug
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The platform identity this person already has, if any.
|
|
45
|
+
*
|
|
46
|
+
* One human, one email, one profile — whichever way they sign in. Every provider that reaches
|
|
47
|
+
* `linkProfile` establishes the email first (Google asserts a verified address, the OTP flow
|
|
48
|
+
* proves possession of it, the supervisor key is a full-trust developer credential), so the
|
|
49
|
+
* name is the identity and a second method is a second CREDENTIAL on it, never a second person.
|
|
50
|
+
* Without this the platform minted a whole new organization per method: the same address signed
|
|
51
|
+
* in by Google and by key ended up in two entities, and each could see only the projects the
|
|
52
|
+
* other had not created.
|
|
53
|
+
*
|
|
54
|
+
* Two rows can carry one address, and only one of them is a platform login. `inviteUser`
|
|
55
|
+
* (`@owlmeans/iam-integrated`) writes an END USER record for the same person — the identity the
|
|
56
|
+
* GENERATED application authenticates, deliberately kept apart from the platform credential.
|
|
57
|
+
* Those rows carry no login service, which is what tells the two apart here: this service is
|
|
58
|
+
* the only writer of `credential = "service:{type}:{service}"`, so a profile that has one is a
|
|
59
|
+
* platform login and a profile that has none is somebody's end user.
|
|
60
|
+
*/
|
|
61
|
+
const findPlatformIdentity = async (username: string): Promise<IdentityProfile | null> => {
|
|
62
|
+
const name = username.trim()
|
|
63
|
+
if (name === '') return null
|
|
64
|
+
|
|
65
|
+
const ctx = service.ctx as Context
|
|
66
|
+
const accountResource = ctx.resource<IdentityAccountResource>(AUTH_IDENTITY_ACCOUNT)
|
|
67
|
+
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
68
|
+
|
|
69
|
+
const { items: accounts } = await accountResource.list({ name } as Criteria<IdentityAccount>)
|
|
70
|
+
for (const account of accounts) {
|
|
71
|
+
const { items: profiles } = await profileResource.list({ userId: account.id } as Criteria<IdentityProfile>)
|
|
72
|
+
const platform = profiles.find(profile =>
|
|
73
|
+
profile.credential?.startsWith(`${LOGIN_SERVICE_PREFIX}${EXTERNAL_KEY_DELIMITER}`) === true
|
|
74
|
+
)
|
|
75
|
+
if (platform != null) return platform
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
const service: IdentityLinkingService = appendContextual<IdentityLinkingService>(AUTH_IDENTITY_LINKING, {
|
|
44
82
|
getLinkedProfile: async (details: ProviderProfileDetails): Promise<AuthPayload | null> => {
|
|
45
83
|
const ctx = service.ctx as Context
|
|
@@ -74,6 +112,29 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
|
|
|
74
112
|
const entityResource = ctx.resource<OrgEntityResource>(AUTH_IDENTITY_ORG_ENTITY)
|
|
75
113
|
const resolver = ctx.service<EntityResolverService>(ENTITY_RESOLVER)
|
|
76
114
|
|
|
115
|
+
// A person the platform already knows is LINKED, not registered again. Everything below
|
|
116
|
+
// this point creates an organization, an account and a profile, and doing that for a second
|
|
117
|
+
// login method is what put one email in two entities.
|
|
118
|
+
const known = meta.force === true ? null : await findPlatformIdentity(meta.username)
|
|
119
|
+
if (known != null) {
|
|
120
|
+
await credsResource.create({
|
|
121
|
+
challenge: '',
|
|
122
|
+
type: details.type,
|
|
123
|
+
userId: externalKey(details),
|
|
124
|
+
profileId: known.profileId,
|
|
125
|
+
credential: loginService(details),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
type: details.type,
|
|
130
|
+
role: known.role,
|
|
131
|
+
userId: known.userId ?? known.profileId,
|
|
132
|
+
profileId: known.profileId,
|
|
133
|
+
entitySlug: await slugOf(known.entityId),
|
|
134
|
+
scopes: known.scopes,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
77
138
|
// Every account gets its own organization. The entity record is created FIRST because it
|
|
78
139
|
// owns the two values that outlive everything else here: the id the account and its profiles
|
|
79
140
|
// are keyed by, and the frozen `iamKey` that realms and object names are minted from.
|
|
@@ -170,6 +231,18 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
|
|
|
170
231
|
return result
|
|
171
232
|
},
|
|
172
233
|
|
|
234
|
+
unlinkCredentials: async (details: ProviderProfileDetails): Promise<void> => {
|
|
235
|
+
const ctx = service.ctx as Context
|
|
236
|
+
const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
|
|
237
|
+
const cred = await credsResource.load({
|
|
238
|
+
type: details.type,
|
|
239
|
+
userId: externalKey(details),
|
|
240
|
+
credential: loginService(details),
|
|
241
|
+
})
|
|
242
|
+
if (cred?.id == null) return
|
|
243
|
+
await credsResource.delete(cred.id)
|
|
244
|
+
},
|
|
245
|
+
|
|
173
246
|
getOwnerProfiles: async (entityId: string): Promise<Profile[]> => {
|
|
174
247
|
const ctx = service.ctx as Context
|
|
175
248
|
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
package/src/types.ts
CHANGED
|
@@ -77,6 +77,17 @@ export interface IdentityLinkingService extends InitializedService {
|
|
|
77
77
|
getLinkedProfile: (details: ProviderProfileDetails) => Promise<AuthPayload | null>
|
|
78
78
|
linkProfile: (details: ProviderProfileDetails, meta: AccountMeta) => Promise<AuthPayload>
|
|
79
79
|
linkCredentials: (details: ProviderProfileDetails) => Promise<AuthPayload>
|
|
80
|
+
/**
|
|
81
|
+
* Forget which profile an external login maps to, so the next `linkProfile` establishes it anew.
|
|
82
|
+
*
|
|
83
|
+
* The stored mapping is keyed `{type, userId, credential}` under a unique index, so a caller
|
|
84
|
+
* that has decided the existing mapping is wrong cannot simply write over it — it has to be
|
|
85
|
+
* retired first. That decision belongs to whoever owns the login path (a decorator serving a
|
|
86
|
+
* different population than the platform's own customers, say); the key format belongs here.
|
|
87
|
+
*
|
|
88
|
+
* Idempotent: a login that maps to nothing is already in the state this promises.
|
|
89
|
+
*/
|
|
90
|
+
unlinkCredentials: (details: ProviderProfileDetails) => Promise<void>
|
|
80
91
|
getOwnerProfiles: (entityId: string) => Promise<Profile[]>
|
|
81
92
|
getOwnerCredentials: (userId: string, entityId?: string, type?: string) => Promise<AuthCredentials | undefined>
|
|
82
93
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { AuthRole } from '@owlmeans/auth'
|
|
3
|
+
import { AuthenticationType } from '@owlmeans/auth'
|
|
4
|
+
import { makeIdentityLinkingService } from '../src/service.js'
|
|
5
|
+
import {
|
|
6
|
+
AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_ORG_ENTITY,
|
|
7
|
+
AUTH_IDENTITY_PROFILE,
|
|
8
|
+
} from '../src/consts.js'
|
|
9
|
+
import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One human, one email, one platform profile — whichever way they sign in.
|
|
13
|
+
*
|
|
14
|
+
* `linkProfile` used to create an organization, an account and a profile on every registration,
|
|
15
|
+
* and registration is per METHOD. The same address signing in by Google and then by key ended up
|
|
16
|
+
* in two entities that could not see each other's projects: `requireEntity` matched neither, and
|
|
17
|
+
* 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
|
+
*/
|
|
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
|
+
describe('linkProfile', () => {
|
|
88
|
+
test('a first sign-in registers an organization, an account and a profile', async () => {
|
|
89
|
+
const ctx = makeCtx()
|
|
90
|
+
const payload = await linkingFor(ctx).linkProfile(
|
|
91
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(ctx.resources[AUTH_IDENTITY_ORG_ENTITY].items).toHaveLength(1)
|
|
95
|
+
expect(ctx.resources[AUTH_IDENTITY_ACCOUNT].items).toHaveLength(1)
|
|
96
|
+
expect(ctx.resources[AUTH_IDENTITY_PROFILE].items).toHaveLength(1)
|
|
97
|
+
expect(payload.profileId).toStartWith('google-oauth:')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('a SECOND method on the same email adds a credential, not an identity', async () => {
|
|
101
|
+
// The whole defect: this used to mint a second organization, and the person's projects
|
|
102
|
+
// became invisible to their other login.
|
|
103
|
+
const ctx = makeCtx()
|
|
104
|
+
const linking = linkingFor(ctx)
|
|
105
|
+
|
|
106
|
+
const first = await linking.linkProfile(
|
|
107
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
108
|
+
)
|
|
109
|
+
const second = await linking.linkProfile(
|
|
110
|
+
details(AuthenticationType.Supervisor, 'person@example.org') as never,
|
|
111
|
+
{ username: 'person@example.org' }
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(ctx.resources[AUTH_IDENTITY_ORG_ENTITY].items).toHaveLength(1)
|
|
115
|
+
expect(ctx.resources[AUTH_IDENTITY_ACCOUNT].items).toHaveLength(1)
|
|
116
|
+
expect(ctx.resources[AUTH_IDENTITY_PROFILE].items).toHaveLength(1)
|
|
117
|
+
// Two credentials, one per method — which is the point.
|
|
118
|
+
expect(ctx.resources[AUTH_IDENTITY_CREDENTIALS].items).toHaveLength(2)
|
|
119
|
+
expect(ctx.resources[AUTH_IDENTITY_CREDENTIALS].items.map((c: any) => c.type).sort())
|
|
120
|
+
.toEqual(['google-oauth', AuthenticationType.Supervisor].sort())
|
|
121
|
+
|
|
122
|
+
// Same identity, same organization — so the same projects.
|
|
123
|
+
expect(second.profileId).toBe(first.profileId)
|
|
124
|
+
expect(second.entitySlug).toBe(first.entitySlug)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('a different email is a different person', async () => {
|
|
128
|
+
const ctx = makeCtx()
|
|
129
|
+
const linking = linkingFor(ctx)
|
|
130
|
+
|
|
131
|
+
await linking.linkProfile(details('google-oauth', 'a') as never, { username: 'a@example.org' })
|
|
132
|
+
await linking.linkProfile(details('google-oauth', 'b') as never, { username: 'b@example.org' })
|
|
133
|
+
|
|
134
|
+
expect(ctx.resources[AUTH_IDENTITY_ORG_ENTITY].items).toHaveLength(2)
|
|
135
|
+
expect(ctx.resources[AUTH_IDENTITY_PROFILE].items).toHaveLength(2)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("an organization's END USER row is never mistaken for a platform login", async () => {
|
|
139
|
+
// `inviteUser` writes a row for the same person carrying the same address — the identity the
|
|
140
|
+
// GENERATED application authenticates, deliberately separate from the platform credential. It
|
|
141
|
+
// has no login service on it, and that is what tells the two apart.
|
|
142
|
+
const ctx = makeCtx({
|
|
143
|
+
accounts: [{ id: 'acc-end-user', credential: 'x', name: 'person@example.org', entityId: 'ent-1' }],
|
|
144
|
+
profiles: [{
|
|
145
|
+
id: 'prof-end-user', profileId: 'email-otp:acc-end-user', userId: 'acc-end-user',
|
|
146
|
+
role: AuthRole.User, name: 'person@example.org', email: 'person@example.org',
|
|
147
|
+
entityId: 'ent-1', scopes: ['*'],
|
|
148
|
+
}],
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const payload = await linkingFor(ctx).linkProfile(
|
|
152
|
+
details('google-oauth', 'google-sub') as never, { username: 'person@example.org' }
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
// A new platform identity, not a hijack of the end-user row.
|
|
156
|
+
expect(payload.profileId).toStartWith('google-oauth:')
|
|
157
|
+
expect(ctx.resources[AUTH_IDENTITY_PROFILE].items).toHaveLength(2)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('force still registers a fresh identity', async () => {
|
|
161
|
+
const ctx = makeCtx()
|
|
162
|
+
const linking = linkingFor(ctx)
|
|
163
|
+
|
|
164
|
+
await linking.linkProfile(details('google-oauth', 'a') as never, { username: 'person@example.org' })
|
|
165
|
+
await linking.linkProfile(
|
|
166
|
+
details('google-oauth', 'b') as never, { username: 'person@example.org', force: true }
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
expect(ctx.resources[AUTH_IDENTITY_PROFILE].items).toHaveLength(2)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Forgetting which profile an external login maps to.
|
|
175
|
+
*
|
|
176
|
+
* The mapping is unique on `{type, userId, credential}`, so a caller that has decided the stored
|
|
177
|
+
* one is wrong cannot write over it — it has to be retired first. That is what lets a login path
|
|
178
|
+
* serving a different population than this service's own customers (a target project's end users,
|
|
179
|
+
* say) re-establish an identity of its own for an address that was merged onto a platform one.
|
|
180
|
+
*/
|
|
181
|
+
describe('unlinkCredentials', () => {
|
|
182
|
+
test('the stored mapping is removed, and only that one', async () => {
|
|
183
|
+
const ctx = makeCtx()
|
|
184
|
+
const linking = linkingFor(ctx)
|
|
185
|
+
await linking.linkProfile(details('email-otp', 'sub-1'), { username: 'person@example.org' })
|
|
186
|
+
await linking.linkProfile(details('google', 'sub-2'), { username: 'person@example.org' })
|
|
187
|
+
expect(ctx.resources[AUTH_IDENTITY_CREDENTIALS].items.length).toBe(2)
|
|
188
|
+
|
|
189
|
+
await linking.unlinkCredentials(details('email-otp', 'sub-1'))
|
|
190
|
+
|
|
191
|
+
expect(ctx.resources[AUTH_IDENTITY_CREDENTIALS].items.map((c: any) => c.type))
|
|
192
|
+
.toEqual(['google'])
|
|
193
|
+
// And the login is unlinked as far as every reader is concerned.
|
|
194
|
+
expect(await linking.getLinkedProfile(details('email-otp', 'sub-1'))).toBeNull()
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('a login that maps to nothing is already in the state this promises', async () => {
|
|
198
|
+
const linking = linkingFor(makeCtx())
|
|
199
|
+
|
|
200
|
+
expect(await linking.unlinkCredentials(details('email-otp', 'nobody'))).toBeUndefined()
|
|
201
|
+
})
|
|
202
|
+
})
|