@owlmeans/server-auth-identity 0.1.18-rc.5 → 0.1.18-rc.50

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.
Files changed (107) hide show
  1. package/README.md +265 -57
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-auth-identity/SKILL.md +194 -98
  4. package/build/consts.d.ts +15 -0
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +20 -0
  7. package/build/consts.js.map +1 -1
  8. package/build/consts.local.d.ts +15 -0
  9. package/build/consts.local.d.ts.map +1 -0
  10. package/build/consts.local.js +15 -0
  11. package/build/consts.local.js.map +1 -0
  12. package/build/events.d.ts +9 -0
  13. package/build/events.d.ts.map +1 -0
  14. package/build/events.js +43 -0
  15. package/build/events.js.map +1 -0
  16. package/build/groups/types.d.ts +36 -0
  17. package/build/groups/types.d.ts.map +1 -0
  18. package/build/groups/types.js +2 -0
  19. package/build/groups/types.js.map +1 -0
  20. package/build/groups.d.ts +13 -0
  21. package/build/groups.d.ts.map +1 -0
  22. package/build/groups.js +57 -0
  23. package/build/groups.js.map +1 -0
  24. package/build/helper.d.ts +6 -1
  25. package/build/helper.d.ts.map +1 -1
  26. package/build/helper.js +19 -3
  27. package/build/helper.js.map +1 -1
  28. package/build/iam/types.d.ts +22 -0
  29. package/build/iam/types.d.ts.map +1 -0
  30. package/build/iam/types.js +2 -0
  31. package/build/iam/types.js.map +1 -0
  32. package/build/identity/types.d.ts +35 -0
  33. package/build/identity/types.d.ts.map +1 -0
  34. package/build/identity/types.js +2 -0
  35. package/build/identity/types.js.map +1 -0
  36. package/build/identity.d.ts +16 -0
  37. package/build/identity.d.ts.map +1 -0
  38. package/build/identity.js +176 -0
  39. package/build/identity.js.map +1 -0
  40. package/build/index.d.ts +9 -1
  41. package/build/index.d.ts.map +1 -1
  42. package/build/index.js +5 -1
  43. package/build/index.js.map +1 -1
  44. package/build/keys/types.d.ts +22 -0
  45. package/build/keys/types.d.ts.map +1 -0
  46. package/build/keys/types.js +2 -0
  47. package/build/keys/types.js.map +1 -0
  48. package/build/keys.d.ts +4 -0
  49. package/build/keys.d.ts.map +1 -0
  50. package/build/keys.js +16 -0
  51. package/build/keys.js.map +1 -0
  52. package/build/native.d.ts +14 -0
  53. package/build/native.d.ts.map +1 -0
  54. package/build/native.js +12 -0
  55. package/build/native.js.map +1 -0
  56. package/build/resolver.d.ts +3 -0
  57. package/build/resolver.d.ts.map +1 -0
  58. package/build/resolver.js +147 -0
  59. package/build/resolver.js.map +1 -0
  60. package/build/resource.d.ts +14 -1
  61. package/build/resource.d.ts.map +1 -1
  62. package/build/resource.js +39 -8
  63. package/build/resource.js.map +1 -1
  64. package/build/service.d.ts +6 -2
  65. package/build/service.d.ts.map +1 -1
  66. package/build/service.js +107 -127
  67. package/build/service.js.map +1 -1
  68. package/build/types.d.ts +219 -15
  69. package/build/types.d.ts.map +1 -1
  70. package/build/types.local.d.ts +11 -0
  71. package/build/types.local.d.ts.map +1 -0
  72. package/build/types.local.js +2 -0
  73. package/build/types.local.js.map +1 -0
  74. package/build/utils/native/types.d.ts +17 -0
  75. package/build/utils/native/types.d.ts.map +1 -0
  76. package/build/utils/native/types.js +2 -0
  77. package/build/utils/native/types.js.map +1 -0
  78. package/build/utils/native.d.ts +4 -0
  79. package/build/utils/native.d.ts.map +1 -0
  80. package/build/utils/native.js +8 -0
  81. package/build/utils/native.js.map +1 -0
  82. package/package.json +12 -9
  83. package/src/consts.local.ts +15 -0
  84. package/src/consts.ts +24 -0
  85. package/src/events.ts +61 -0
  86. package/src/groups/types.ts +36 -0
  87. package/src/groups.ts +86 -0
  88. package/src/helper.ts +21 -3
  89. package/src/identity/types.ts +38 -0
  90. package/src/identity.ts +207 -0
  91. package/src/index.ts +9 -1
  92. package/src/keys/types.ts +22 -0
  93. package/src/keys.ts +24 -0
  94. package/src/resolver.ts +177 -0
  95. package/src/resource.ts +45 -10
  96. package/src/service.ts +122 -143
  97. package/src/types.local.ts +14 -0
  98. package/src/types.ts +232 -15
  99. package/src/utils/native/types.ts +17 -0
  100. package/src/utils/native.ts +17 -0
  101. package/tests/context.ts +215 -0
  102. package/tests/events.spec.ts +89 -0
  103. package/tests/identity.spec.ts +149 -0
  104. package/tests/linking.spec.ts +164 -0
  105. package/tests/resolver.spec.ts +149 -0
  106. package/tsconfig.json +1 -1
  107. /package/src/{iam.ts → iam/types.ts} +0 -0
package/README.md CHANGED
@@ -1,97 +1,305 @@
1
1
  # @owlmeans/server-auth-identity
2
2
 
3
- Mongo-backed local identity resources and provider account linking for OwlMeans server applications.
4
-
5
- ## Overview
6
-
7
- - Registers three Mongo resources: local accounts, local profiles, and provider credentials.
8
- - Provides `IdentityLinkingService` to map external provider profile details into an OwlMeans `AuthPayload`.
9
- - Keeps external provider identity separate from local product identity and authorization scopes.
10
- - Designed to work with `@owlmeans/server-auth`, `@owlmeans/server-oidc-rp`, and product-specific module gates.
3
+ The Mongo-backed local identity store behind provider logins. An application uses it when it owns
4
+ its users and organizations: a Google, OIDC, email-OTP or supervisor login is mapped onto one local
5
+ account, profile and credential, and the organization entity registry turns the slug on a token into
6
+ a stable entity id. It is not used where identity and authorization are delegated to an external IAM
7
+ whose grants decide access (use `@owlmeans/server-oidc-rp`'s guard and gate, or
8
+ `@owlmeans/server-iam`), and it does not verify bearer tokens (that is `@owlmeans/server-auth`).
11
9
 
12
10
  ## Installation
13
11
 
14
12
  ```bash
15
- bun add @owlmeans/server-auth-identity
13
+ bun add @owlmeans/server-auth-identity@^0.1.18-rc.50
16
14
  ```
17
15
 
16
+ ## Concepts
17
+
18
+ - **Organization entity (`OrgEntity`)** — the customer organization: `id` (stable `entityId`,
19
+ never on the wire), `slug` (renameable `entitySlug`, the only value a token carries),
20
+ `formerSlugs` that keep resolving after a rename, a frozen `iamKey`, and `names` minted once for
21
+ systems that cannot be renamed.
22
+ - **Account (`IdentityAccount`)** — one per person: a unique, lower-case `email`, a display `name`,
23
+ and `entityId`, the personal organization created with it.
24
+ - **Credentials (`IdentityCredentials`)** — one row per sign-in method of an account (`accountId`),
25
+ keyed by `"{type}:{service}:{providerSub}"`.
26
+ - **Profile row (`IdentityProfile`)** — one person in one organization for one app (`service`):
27
+ `profileId` (computed by `identityKeyHelper.profileIdOf(service, accountId)`), `owner`, `role`, `scopes`,
28
+ `permissions`, `groups`. The row in the account's own organization is the app's PRIMARY row.
29
+ - **Groups (`OrgGroup`)** — an app's groups, kept in the organization record, unique per
30
+ (`service`, `key`).
31
+ - **Primitives** — `identityOf(ctx).ensureAccount` (find-or-register an account by method, then
32
+ address) and `identityOf(ctx).ensureProfile` (find-or-create a row, primary row first).
33
+ - **Linking service** — `IdentityLinkingService` signs a provider login into the deployment's own
34
+ app and returns an `AuthPayload`.
35
+ - **Entity resolver** — the `EntityResolverService` registered under `ENTITY_RESOLVER`. Registering
36
+ it tells the server boundary that this deployment has organizations.
37
+ - **Identity events** — `identityEvents(ctx)` returns the `IdentityEventsService`:
38
+ `onProfileCreated` runs once per (account, app), `onEntityCreated` once per registration.
39
+
18
40
  ## Usage
19
41
 
20
- Register identity resources in a server context after Mongo and auth services are available:
42
+ ### Register in a context
43
+
44
+ Register after the Mongo service and `appendAuthService`. A dedicated db config under
45
+ `AUTH_IDENTITY_DB_ALIAS` scopes a collection prefix to the identity collections only.
21
46
 
22
- ```typescript
23
- import { appendAuthIdentityResources } from '@owlmeans/server-auth-identity'
47
+ ```ts
48
+ // config.ts
49
+ import { AUTH_IDENTITY_DB_ALIAS, AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
24
50
 
25
- appendAuthIdentityResources(context)
51
+ cfg.dbs.push({
52
+ alias: AUTH_IDENTITY_DB_ALIAS,
53
+ service: MONGO_SERVICE,
54
+ host: process.env.MONGO_HOST!,
55
+ schema: 'my-app',
56
+ secret: process.env.MONGO_SECRET!,
57
+ resourcePrefix: 'my-app-', // -> my-app-account, my-app-profile, ...
58
+ })
59
+
60
+ // let the OIDC relying party link provider logins through this store
61
+ cfg.oidc.accountLinkingService = AUTH_IDENTITY_LINKING
26
62
  ```
27
63
 
28
- Link an external provider profile to a local account/profile:
64
+ ```ts
65
+ // context.ts
66
+ import { appendAuthService } from '@owlmeans/server-auth'
67
+ import { appendAuthIdentityResources, AUTH_IDENTITY_DB_ALIAS } from '@owlmeans/server-auth-identity'
29
68
 
30
- ```typescript
31
- import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
32
- import type { IdentityLinkingService } from '@owlmeans/server-auth-identity'
69
+ appendMongo<C, T>(context)
70
+ appendAuthService<C, T>(context)
71
+ // MY_APP: this deployment's own app key — the `service` of the rows its sign-in writes
72
+ appendAuthIdentityResources(context, AUTH_IDENTITY_DB_ALIAS, { service: MY_APP })
73
+ context.registerService(makeMyAppGate())
74
+ ```
33
75
 
34
- const linking = context.service<IdentityLinkingService>(AUTH_IDENTITY_LINKING)
76
+ ### A product gate over profile scopes
77
+
78
+ ```ts
79
+ import { createLazyService } from '@owlmeans/context'
80
+ import type { GateService } from '@owlmeans/entrypoint'
81
+ import { AuthForbidden } from '@owlmeans/auth'
82
+ import { makeEntityScope } from '@owlmeans/auth-common'
83
+ import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity'
84
+ import type { IdentityProfileResource } from '@owlmeans/server-auth-identity'
35
85
 
36
- const auth = await linking.linkProfile(providerDetails, { username })
86
+ export const makeMyAppGate = (alias: string = MY_APP_GATE): GateService => {
87
+ const service: GateService = createLazyService<GateService>(alias, {
88
+ assert: async (req, _, params) => {
89
+ await service.ready()
90
+ const ctx = service.assertCtx<Config, Context>()
91
+
92
+ const entityId = makeEntityScope(req).entityKeyOf()
93
+ if (req.auth == null || entityId == null) {
94
+ throw new AuthForbidden('auth')
95
+ }
96
+
97
+ const profile = await ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
98
+ .load({ entityId, profileId: req.auth.profileId })
99
+ // The store also holds other apps' rows of the same organization — only this app's count.
100
+ if (profile == null || profile.service !== MY_APP || profile.disabled === true
101
+ || (profile.expiresAt != null && new Date(profile.expiresAt) < new Date())) {
102
+ throw new AuthForbidden('profile')
103
+ }
104
+
105
+ const scopes = req.auth.scopes ?? []
106
+ const required = params.map(param => param.replace(/\{entity\}/g, entityId))
107
+ if (!required.every(scope => scopes.includes('*') || scopes.includes(scope))) {
108
+ throw new AuthForbidden('permission')
109
+ }
110
+ },
111
+ })
112
+
113
+ return service
114
+ }
37
115
  ```
38
116
 
39
- Read identity profiles without mutating records:
117
+ A protocol uses it with `{ guards: DEFAULT_GUARD, gate: { alias: MY_APP_GATE, params: ['my-app-project-{entity}'] } }`.
40
118
 
41
- ```typescript
119
+ ### Read an organization's members
120
+
121
+ ```ts
122
+ import { handlers } from '@owlmeans/server-app'
123
+ import { makeEntityScope } from '@owlmeans/auth-common'
42
124
  import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity'
43
125
  import type { IdentityProfileResource } from '@owlmeans/server-auth-identity'
44
126
 
45
- const profiles = context.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
46
- const profile = await profiles.load(profileId, 'profileId')
127
+ const api = handlers<Context>()
128
+
129
+ export const members = api.request(memberProtocols.list, async (request, context) => {
130
+ const { items } = await context.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
131
+ .list({ entityId: makeEntityScope(request).requireEntityKey(), service: MY_APP }, { size: 0, sort: [{ field: 'createdAt', order: 'desc' }] })
47
132
 
48
- const { items } = await profiles.list({ entityId, profileId } as any)
133
+ return items.map(({ profileId, role, name }) => ({ profileId, role, name }))
134
+ })
49
135
  ```
50
136
 
51
- ## API
137
+ ### Link a provider login
138
+
139
+ ```ts
140
+ import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
141
+ import type { IdentityLinkingService } from '@owlmeans/server-auth-identity'
142
+
143
+ const linking = context.service<IdentityLinkingService>(AUTH_IDENTITY_LINKING)
52
144
 
53
- ### Resource Factories
145
+ const details = {
146
+ type: 'google-oauth', // the AuthenticationType
147
+ service: 'google', // the provider service alias
148
+ clientId: 'google', // the provider client this login came through
149
+ userId: providerSub, // the provider's subject claim
150
+ }
151
+
152
+ // Returning login: the credential, its account, this app's row in the account's organization.
153
+ let payload = await linking.getLinkedProfile(details)
154
+ // Otherwise: the account by the verified address (registered if new), the method attached to it,
155
+ // and this app's owner row in the person's own organization.
156
+ payload ??= await linking.linkProfile(details, { username: 'person@example.org' })
157
+ // payload: { type, role, userId: accountId, profileId, entitySlug, scopes }
158
+ ```
159
+
160
+ ### Rows for a hosted app
54
161
 
55
- - `makeIdentityAccountResource(dbAlias?)` - local account record, one per user.
56
- - `makeIdentityProfileResource(dbAlias?)` - local profile record with `entityId`, `role`, `scopes`, and optional expiry.
57
- - `makeIdentityCredentialsResource(dbAlias?)` - provider credentials link record.
58
- - `appendAuthIdentityResources(context, dbAlias?)` - registers all resources and `IdentityLinkingService`.
162
+ ```ts
163
+ import { identityOf } from '@owlmeans/server-auth-identity'
59
164
 
60
- ### Aliases
165
+ const identity = identityOf(context)
166
+ const { account } = await identity.ensureAccount({ email }, details) // writes no row
167
+ const row = await identity.ensureProfile({ account, service: clientId, entityId: account.entityId, owner: true })
168
+ // a hosted app's rows carry `scopes: []`; a row in another organization brings the primary row with it
169
+ ```
61
170
 
62
- - `AUTH_IDENTITY_ACCOUNT` - account resource alias.
63
- - `AUTH_IDENTITY_PROFILE` - profile resource alias.
64
- - `AUTH_IDENTITY_CREDENTIALS` - provider credentials resource alias.
65
- - `AUTH_IDENTITY_LINKING` - linking service alias.
171
+ ### Provision when a person becomes a user of the app
66
172
 
67
- ### Types
173
+ ```ts
174
+ import { identityEvents } from '@owlmeans/server-auth-identity'
68
175
 
69
- - `IdentityAccount` - `Profile & ResourceRecord`; `credential` is the generated local entity slug.
70
- - `IdentityProfile` - `Profile & ResourceRecord`; includes `profileId`, `userId?`, `role`, `entityId`, `scopes`, `expiresAt?`.
71
- - `IdentityCredentials` - `AuthCredentials & ResourceRecord`; includes `profileId` and derived provider keys.
72
- - `IdentityLinkingService` - `getLinkedProfile`, `linkProfile`, `linkCredentials`, `getOwnerProfiles`, `getOwnerCredentials`.
176
+ // in makeContext, after appendAuthIdentityResources
177
+ identityEvents(context)?.onProfileCreated(async (event, ctx) => {
178
+ if (event.service !== MY_APP) return
179
+ await ctx.service<PlanService>(PLAN_SERVICE).grantStarterPlan(event.entityId)
180
+ })
181
+ ```
182
+
183
+ `onProfileCreated` carries `entityId`, `entitySlug`, `accountId`, `profileId`, `service` and
184
+ `owner`, and fires once per (account, app), when its primary row is created. `onEntityCreated`
185
+ carries `entityId`, `entitySlug`, `iamKey`, `accountId`, `profileId`, `username`, the login `type`
186
+ and `service`, the owner row's app as `profileService`, and `createdAt`; `linkProfile` fires it when
187
+ the sign-in registered the person. Listeners run in order and are awaited; one that throws is logged
188
+ and never fails the sign-in, so anything a listener provisions needs its own backfill.
189
+
190
+ ### Rename an organization and mint a durable name
191
+
192
+ ```ts
193
+ import { ENTITY_RESOLVER, makeEntityScope } from '@owlmeans/auth-common'
194
+ import type { EntityResolverService } from '@owlmeans/auth-common'
73
195
 
74
- ## Key Derivation
196
+ export const rename = api.body(organizationProtocols.rename, async ({ slug }, context, request) => {
197
+ const resolver = context.service<EntityResolverService>(ENTITY_RESOLVER)
198
+ const entity = await resolver.rename(makeEntityScope(request).requireEntityKey(), slug) // old slug moves to formerSlugs
75
199
 
76
- - Account `credential`: generated Base58 local entity slug.
77
- - Profile `profileId`: `"{type}:{accountId}"`.
78
- - Credentials `userId`: `"{type}:{service}:{providerSub}"`.
79
- - Credentials `credential`: `"service:{type}:{service}"`.
200
+ return { slug: entity.slug }
201
+ })
80
202
 
81
- ## Product-Viable Integration Notes
203
+ // read once, or mint and persist on first ask; later renames leave it alone
204
+ const namespace = await resolver.mintName(entityId, 'namespace', entity => `my-app-${entity.iamKey}`)
205
+ ```
206
+
207
+ ## API
82
208
 
83
- - Google/OIDC login is only the provider bootstrap path; durable authorization data lives in `IdentityProfile`.
84
- - Backend contexts should register `appendAuthService(context)`, then `appendAuthIdentityResources(context)`, then product gate services.
85
- - Product gates should read `AUTH_IDENTITY_PROFILE`, verify `entityId`, reject expired or blocked profiles, and compare gate params against profile scopes.
86
- - `Resource.pick()` is destructive and deletes the matching record. Never use `pick()` for gate or handler reads; use `load()` or `list()`.
209
+ ### Functions
210
+
211
+ | Symbol | Kind | Purpose |
212
+ |---|---|---|
213
+ | `appendAuthIdentityResources(context, dbAlias?, { service? })` | function | Register the four resources, the linking service for app `service`, the entity resolver and (unless one is registered) the identity-events service |
214
+ | `identityKeyHelper.profileIdOf(service, accountId)` | method | The computed profile id of an (account, app) |
215
+ | `identityOf(ctx).ensureAccount({ email, name? }, details?)` | method | The person's account — by method, then address, else registered with a personal organization |
216
+ | `identityOf(ctx).ensureProfile({ account, service, entityId, … })` | method | Find-or-create a row; the primary row first |
217
+ | `identityKeyHelper.credentialKeyOf(details)`, `identityOf(ctx).credentialOf(details)`, `identityKeyHelper.normalizeEmail(email)` | method | A method's credential key and stored row; the stored address form |
218
+ | `orgGroupsOf(ctx).listOrgGroups`, `.putOrgGroup`, `.removeOrgGroup` | method | An organization's groups of one app, by guarded field-level writes |
219
+ | `makeOrgEntityResource(dbAlias?)` | function | Mongo resource for `OrgEntity` |
220
+ | `makeIdentityAccountResource(dbAlias?)` | function | Mongo resource for `IdentityAccount` |
221
+ | `makeIdentityProfileResource(dbAlias?)` | function | Mongo resource for `IdentityProfile` |
222
+ | `makeIdentityCredentialsResource(dbAlias?)` | function | Mongo resource for `IdentityCredentials` |
223
+ | `makeIdentityLinkingService({ service? })` | function | The `IdentityLinkingService` implementation for one app |
224
+ | `makeEntityResolverService(alias = ENTITY_RESOLVER)` | function | The `EntityResolverService` implementation, cached 30 s per resolved name |
225
+ | `makeIdentityEventsService(alias = AUTH_IDENTITY_EVENTS)` | function | The `IdentityEventsService` implementation (lazy) |
226
+ | `identityEvents(ctx, alias?)` | function | The registered events service, or `null` |
227
+
228
+ ### Constants
229
+
230
+ | Symbol | Kind | Purpose |
231
+ |---|---|---|
232
+ | `AUTH_IDENTITY_ORG_ENTITY`, `AUTH_IDENTITY_ACCOUNT`, `AUTH_IDENTITY_PROFILE`, `AUTH_IDENTITY_CREDENTIALS` | const | Resource aliases (`'auth-identity:…'`), lookup keys only |
233
+ | `AUTH_IDENTITY_LINKING` | const | `'auth-identity:linking'` — linking service alias |
234
+ | `AUTH_IDENTITY_EVENTS` | const | `'auth-identity:events'` — identity-events service alias |
235
+ | `AUTH_IDENTITY_DB_ALIAS` | const | `'auth-identity'` — suggested db config alias |
236
+ | `AUTH_IDENTITY_ORG_ENTITY_COLLECTION`, `AUTH_IDENTITY_ACCOUNT_COLLECTION`, `AUTH_IDENTITY_PROFILE_COLLECTION`, `AUTH_IDENTITY_CREDENTIALS_COLLECTION` | const | Colon-free Mongo collection base names |
237
+ | `DEFAULT_APP_SERVICE` | const | `'app'` — the own app key when none is passed |
238
+ | `MAX_ENTITY_SLUG_ATTEMPTS` | const | `8` — word slugs tried before minting gives up |
239
+ | `MAX_ACCOUNT_KEY_ATTEMPTS`, `MAX_GUARDED_UPDATE_ATTEMPTS`, `PROFILE_DIGEST_LENGTH` | const | Retry bounds; the `profileId` digest length |
240
+ | `LOGIN_SERVICE_PREFIX`, `EXTERNAL_KEY_DELIMITER` | const | `'service'` and `':'` — the derived-key grammar |
87
241
 
88
- ## Related Packages
242
+ ### Types
89
243
 
90
- - [`@owlmeans/auth`](../auth) - core `AuthPayload`, `AuthRole`, `Profile`, and auth errors.
91
- - [`@owlmeans/server-auth`](../server-auth) - bearer verification and default auth guard.
92
- - [`@owlmeans/server-oidc-rp`](../server-oidc-rp) - provider exchange and account-linking interface compatibility.
93
- - [`@owlmeans/oidc`](../oidc) - `ProviderProfileDetails` and provider config types.
94
- - [`@owlmeans/mongo-resource`](../mongo-resource) - Mongo-backed resource implementation.
244
+ | Symbol | Kind | Purpose |
245
+ |---|---|---|
246
+ | `OrgEntity` | type | `id`, `slug`, `formerSlugs?`, `iamKey`, `title?`, `names?`, `groups?`, `createdAt`, `updatedAt?` |
247
+ | `OrgGroup`, `OrgGroupBundle`, `OrgGroupFilter` | type | An app's group in an organization and its permission bundles |
248
+ | `IdentityAccount` | type | `id`, `credential`, `email`, `name`, `entityId`, `scopes` |
249
+ | `IdentityProfile` | type | `id`, `profileId`, `userId`, `service`, `entityId`, `owner?`, `role`, `scopes`, `permissions`, `groups?`, `home?`, `disabled?`, `managed?` |
250
+ | `IdentityCredentials` | type | `AuthCredentials` + `accountId` (`profileId?` reserved) |
251
+ | `EnsureAccountArgs`, `EnsuredAccount`, `EnsureProfileArgs`, `IdentityResourcesOptions` | type | The primitives' arguments and results |
252
+ | `IdentityLinkingService` | type | `getLinkedProfile`, `linkProfile`, `linkCredentials`, `unlinkCredentials`, `getOwnerProfiles`, `getOwnerCredentials` |
253
+ | `AccountMeta` | type | `{ username }` — the verified address |
254
+ | `IdentityEventsService` | type | `onEntityCreated` / `propagateEntityCreated`, `onProfileCreated` / `propagateProfileCreated` |
255
+ | `EntityCreatedEvent`, `ProfileCreatedEvent` and their callbacks | type | The event payloads; `(event, ctx) => Promise<void>` |
256
+ | `OrgEntityResource`, `IdentityAccountResource`, `IdentityProfileResource`, `IdentityCredentialsResource` | type | Typed `MongoResource` aliases |
257
+ | `IdentityConfig`, `IdentityContext` | type | Server config and context shapes |
258
+ | `GoogleUserInfo` | type | Google userinfo claims |
259
+ | `EmailIdentityArgs`, `EmailIdentityPayload`, `IdentityIamExtension` | type | Declaration-only seam; nothing in the package consumes them |
260
+
261
+ ## Key derivation
262
+
263
+ - Account `credential` — a unique 16-character Base58 key.
264
+ - Account `email` — trimmed, lower-case, unique.
265
+ - Account / profile `entityId`, profile `userId` and `home`, credentials `accountId` — record ids,
266
+ declared ObjectId references; a first registration creates the organization first.
267
+ - Profile `profileId` — `identityKeyHelper.profileIdOf(service, accountId)`: `"{service}:"` + 22 Base58 characters of
268
+ `sha256("{accountId}:{service}")`. Computed, the same on every row of one (account, app).
269
+ - Credentials `userId` — `"{type}:{service}:{providerSub}"`; unrelated to the profile's `userId`.
270
+ - Credentials `credential` — `"service:{type}:{service}"`.
271
+
272
+ ## Common pitfalls
273
+
274
+ - `Resource.take()` deletes the record it returns. Use `load(where)` or `list(where)` in gates and
275
+ handlers, never `take()`.
276
+ - Query profiles and organization records by `entityId` from
277
+ `makeEntityScope(request).requireEntityKey()` / `.entityKeyOf()`, never by the slug.
278
+ - Without the resolver `request.entity` stays undefined and consumers fall back to the slug — call
279
+ `appendAuthIdentityResources` (or register the resolver) in every service that serves organization
280
+ data.
281
+ - `linkProfile` rows get `ALL_SCOPES` and `AuthRole.User`; a hosted app's rows (`ensureProfile`
282
+ default) get `[]`. A gate of the own app must also require `profile.service`.
283
+ - The address is the identity: whoever names an address to `ensureAccount` / `linkProfile` gets that
284
+ person's account, so only a verified address may reach them.
285
+ - Never `update()` an organization record — write fields (the resolver, the group helpers, a guarded
286
+ `collection.updateOne`), or a stale read erases groups and minted names.
287
+ - Provisioning in an `onEntityCreated` listener is best-effort: a throwing listener is logged and the
288
+ sign-in succeeds, so reconcile what it provisions periodically.
289
+ - The resolver caches for 30 seconds, so a rename reaches other replicas within that window; the old
290
+ slug keeps resolving meanwhile.
291
+ - `rename` rejects a malformed slug and any slug an entity has ever answered to; `mintSlug` throws
292
+ `entity:slug-exhausted` after `MAX_ENTITY_SLUG_ATTEMPTS`.
293
+
294
+ ## Related packages
295
+
296
+ - [`@owlmeans/server-auth`](../server-auth) — bearer verification; canonicalizes the slug through the resolver
297
+ - [`@owlmeans/auth-common`](../auth-common) — `EntityResolverService`, `ENTITY_RESOLVER`, `makeEntityScope` (`requireEntityKey`, `attachEntity`)
298
+ - [`@owlmeans/auth`](../auth) — `AuthPayload`, `AuthRole`, `Profile`, errors
299
+ - [`@owlmeans/server-oidc-rp`](../server-oidc-rp) — calls the linking service during an OAuth callback
300
+ - [`@owlmeans/server-auth-otp`](../server-auth-otp) — email OTP plugin resolving users through the linking service
301
+ - [`@owlmeans/oidc`](../oidc) — `ProviderProfileDetails`
302
+ - [`@owlmeans/mongo-resource`](../mongo-resource) — the Mongo resource implementation
95
303
 
96
304
  <!-- owlmeans:agent-guidance:start -->
97
305
  ## Agent guidance
@@ -101,7 +309,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
101
309
  your project's skill store (`.agents/skills/`):
102
310
 
103
311
  ```sh
104
- npx @owlmeans/agent-skills
312
+ npx @owlmeans/agent-skills@^0.1.18-rc.54
105
313
  ```
106
314
 
107
315
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-auth-identity",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.498Z",
4
+ "version": "0.1.18-rc.50",
5
+ "generatedAt": "2026-10-15T10:20:01.287Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {