@owlmeans/server-auth-identity 0.1.18-rc.0 → 0.1.18-rc.10

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.
@@ -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.10",
5
+ "generatedAt": "2026-09-01T12:04:42.697Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/server-auth-identity
9
9
 
10
10
  **Layer:** Server
11
- **Install:** `"@owlmeans/server-auth-identity": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-auth-identity": "^0.1.18-rc.10"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
package/build/consts.d.ts CHANGED
@@ -2,10 +2,13 @@ export declare const AUTH_IDENTITY_ACCOUNT = "auth-identity:account";
2
2
  export declare const AUTH_IDENTITY_PROFILE = "auth-identity:profile";
3
3
  export declare const AUTH_IDENTITY_CREDENTIALS = "auth-identity:credentials";
4
4
  export declare const AUTH_IDENTITY_LINKING = "auth-identity:linking";
5
+ export declare const AUTH_IDENTITY_ORG_ENTITY = "auth-identity:org-entity";
5
6
  export declare const AUTH_IDENTITY_ACCOUNT_COLLECTION = "account";
6
7
  export declare const AUTH_IDENTITY_PROFILE_COLLECTION = "profile";
7
8
  export declare const AUTH_IDENTITY_CREDENTIALS_COLLECTION = "credentials";
9
+ export declare const AUTH_IDENTITY_ORG_ENTITY_COLLECTION = "org-entity";
8
10
  export declare const AUTH_IDENTITY_DB_ALIAS = "auth-identity";
11
+ export declare const MAX_ENTITY_SLUG_ATTEMPTS = 8;
9
12
  export declare const LOGIN_SERVICE_PREFIX = "service";
10
13
  export declare const EXTERNAL_KEY_DELIMITER = ":";
11
14
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAM5D,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,oCAAoC,gBAAgB,CAAA;AAIjE,eAAO,MAAM,sBAAsB,kBAAkB,CAAA;AAGrD,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C,eAAO,MAAM,sBAAsB,MAAM,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAMlE,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,gCAAgC,YAAY,CAAA;AACzD,eAAO,MAAM,oCAAoC,gBAAgB,CAAA;AACjE,eAAO,MAAM,mCAAmC,eAAe,CAAA;AAI/D,eAAO,MAAM,sBAAsB,kBAAkB,CAAA;AAIrD,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAGzC,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C,eAAO,MAAM,sBAAsB,MAAM,CAAA"}
package/build/consts.js CHANGED
@@ -6,6 +6,7 @@ export const AUTH_IDENTITY_ACCOUNT = 'auth-identity:account';
6
6
  export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile';
7
7
  export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials';
8
8
  export const AUTH_IDENTITY_LINKING = 'auth-identity:linking';
9
+ export const AUTH_IDENTITY_ORG_ENTITY = 'auth-identity:org-entity';
9
10
  // Colon-free Mongo collection base names. The final collection name is
10
11
  // `${resourcePrefix}${collection}` — e.g. with the project prefix 'viam-' this yields
11
12
  // 'viam-account'. Register the identity resources against a db config that carries the
@@ -13,9 +14,13 @@ export const AUTH_IDENTITY_LINKING = 'auth-identity:linking';
13
14
  export const AUTH_IDENTITY_ACCOUNT_COLLECTION = 'account';
14
15
  export const AUTH_IDENTITY_PROFILE_COLLECTION = 'profile';
15
16
  export const AUTH_IDENTITY_CREDENTIALS_COLLECTION = 'credentials';
17
+ export const AUTH_IDENTITY_ORG_ENTITY_COLLECTION = 'org-entity';
16
18
  // Suggested db-config alias for a dedicated config whose resourcePrefix scopes the
17
19
  // collection prefix to identity collections only (not the whole database).
18
20
  export const AUTH_IDENTITY_DB_ALIAS = 'auth-identity';
21
+ // How many slugs to try before giving up on minting a free one. Collisions are settled against a
22
+ // unique index, so this bounds a retry loop rather than a probability.
23
+ export const MAX_ENTITY_SLUG_ATTEMPTS = 8;
19
24
  // Helper prefixes for stable key derivation
20
25
  export const LOGIN_SERVICE_PREFIX = 'service';
21
26
  export const EXTERNAL_KEY_DELIMITER = ':';
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,wFAAwF;AACxF,sFAAsF;AACtF,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAE5D,uEAAuE;AACvE,sFAAsF;AACtF,uFAAuF;AACvF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,oCAAoC,GAAG,aAAa,CAAA;AAEjE,mFAAmF;AACnF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAA;AAErD,4CAA4C;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAC7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,wFAAwF;AACxF,sFAAsF;AACtF,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;AAElE,uEAAuE;AACvE,sFAAsF;AACtF,uFAAuF;AACvF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,gCAAgC,GAAG,SAAS,CAAA;AACzD,MAAM,CAAC,MAAM,oCAAoC,GAAG,aAAa,CAAA;AACjE,MAAM,CAAC,MAAM,mCAAmC,GAAG,YAAY,CAAA;AAE/D,mFAAmF;AACnF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAA;AAErD,iGAAiG;AACjG,uEAAuE;AACvE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAEzC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAC7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA"}
@@ -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;AAIrD,eAAO,MAAM,2BAA2B,YAC7B,YAAY,CAAC,GAAG,CAAC,YAChB,MAAM,KACf,IAKF,CAAA"}
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/helper.js CHANGED
@@ -1,9 +1,15 @@
1
- import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource } from './resource.js';
1
+ import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource, makeOrgEntityResource } from './resource.js';
2
2
  import { makeIdentityLinkingService } from './service.js';
3
+ import { makeEntityResolverService } from './resolver.js';
3
4
  export const appendAuthIdentityResources = (context, dbAlias) => {
4
5
  context.registerResource(makeIdentityAccountResource(dbAlias));
5
6
  context.registerResource(makeIdentityProfileResource(dbAlias));
6
7
  context.registerResource(makeIdentityCredentialsResource(dbAlias));
8
+ context.registerResource(makeOrgEntityResource(dbAlias));
7
9
  context.registerService(makeIdentityLinkingService());
10
+ // Registering the resolver is what tells the server boundary that this deployment HAS
11
+ // organizations: without it `request.entity` stays undefined and every consumer falls back to
12
+ // treating the token's slug as the only entity value there is.
13
+ context.registerService(makeEntityResolverService());
8
14
  };
9
15
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,+BAA+B,EAAE,MAAM,eAAe,CAAA;AACzH,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAEzD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAA0B,EAC1B,OAAgB,EACV,EAAE;IACR,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC,CAAA;IAClE,OAAO,CAAC,eAAe,CAAC,0BAA0B,EAAE,CAAC,CAAA;AACvD,CAAC,CAAA"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAChJ,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AAEzD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAA0B,EAC1B,OAAgB,EACV,EAAE;IACR,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC,CAAA;IAClE,OAAO,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;IACxD,OAAO,CAAC,eAAe,CAAC,0BAA0B,EAAE,CAAC,CAAA;IACrD,sFAAsF;IACtF,8FAA8F;IAC9F,+DAA+D;IAC/D,OAAO,CAAC,eAAe,CAAC,yBAAyB,EAAE,CAAC,CAAA;AACtD,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './consts.js';
2
2
  export type * from './types.js';
3
3
  export * from './resource.js';
4
4
  export * from './service.js';
5
+ export * from './resolver.js';
5
6
  export * from './helper.js';
6
7
  export * from './iam.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
package/build/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './consts.js';
2
2
  export * from './resource.js';
3
3
  export * from './service.js';
4
+ export * from './resolver.js';
4
5
  export * from './helper.js';
5
6
  export * from './iam.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAE3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAE3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { EntityResolverService } from '@owlmeans/auth-common';
2
+ export declare const makeEntityResolverService: (alias?: string) => EntityResolverService;
3
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAgB,MAAM,uBAAuB,CAAA;AAgChF,eAAO,MAAM,yBAAyB,WAC7B,MAAM,KACZ,qBAsIF,CAAA"}
@@ -0,0 +1,138 @@
1
+ import { appendContextual } from '@owlmeans/context';
2
+ import { ENTITY_RESOLVER, ENTITY_SLUG_PATTERN } from '@owlmeans/auth-common';
3
+ import { generateWordSlug, nextSlugCandidate } from '@owlmeans/basic-ids';
4
+ import { AUTH_IDENTITY_ORG_ENTITY, MAX_ENTITY_SLUG_ATTEMPTS } from './consts.js';
5
+ /**
6
+ * How long a resolution is trusted without re-reading the registry.
7
+ *
8
+ * Every authenticated request resolves an entity, so an uncached resolver would put a database
9
+ * round-trip in front of the whole API. The cost of the cache is a rename taking up to this long
10
+ * to be seen by other replicas — which is survivable precisely because the old slug keeps
11
+ * resolving through `formerSlugs` rather than failing.
12
+ */
13
+ const CACHE_TTL = 30_000;
14
+ /** What a stored record id looks like. Anything else can only be a slug or a frozen key. */
15
+ const RECORD_ID = /^[0-9a-f]{24}$/i;
16
+ /** A stored record as the contract exposes it. Persisted records always carry an id. */
17
+ const toRef = (entity) => entity == null ? null : {
18
+ id: entity.id, slug: entity.slug, formerSlugs: entity.formerSlugs, iamKey: entity.iamKey,
19
+ };
20
+ export const makeEntityResolverService = (alias = ENTITY_RESOLVER) => {
21
+ // Keyed by every name a caller might arrive with — id, slug, former slug, iamKey — so a hit
22
+ // costs one lookup regardless of which one it was.
23
+ const cache = new Map();
24
+ const now = () => Date.now();
25
+ const remember = (entity, ...keys) => {
26
+ const at = now();
27
+ for (const key of keys) {
28
+ if (key !== '')
29
+ cache.set(key, { entity, at });
30
+ }
31
+ if (entity != null) {
32
+ cache.set(entity.id, { entity, at });
33
+ cache.set(entity.slug, { entity, at });
34
+ cache.set(entity.iamKey, { entity, at });
35
+ for (const former of entity.formerSlugs ?? [])
36
+ cache.set(former, { entity, at });
37
+ }
38
+ return entity;
39
+ };
40
+ const forget = (entity) => {
41
+ for (const key of [entity.id, entity.slug, entity.iamKey, ...(entity.formerSlugs ?? [])]) {
42
+ if (key != null)
43
+ cache.delete(key);
44
+ }
45
+ };
46
+ const resource = () => service.ctx.resource(AUTH_IDENTITY_ORG_ENTITY);
47
+ const load = async (value) => {
48
+ const cached = cache.get(value);
49
+ if (cached != null && now() - cached.at < CACHE_TTL) {
50
+ return cached.entity;
51
+ }
52
+ const res = resource();
53
+ // Id first: an id is authoritative, so a slug that happens to look like some other entity's
54
+ // id can never shadow it. Then the current slug, then the names it has retired, then the
55
+ // frozen key external systems still quote.
56
+ //
57
+ // The shape test is not an optimisation. `load` reads its argument as a record id, and the
58
+ // store turns that into an ObjectId unconditionally — so handing it a slug throws a driver
59
+ // error out of what is meant to be a lookup that simply misses.
60
+ const byId = RECORD_ID.test(value) ? await res.load(value) : null;
61
+ if (byId != null)
62
+ return remember(byId, value);
63
+ for (const field of ['slug', 'formerSlugs', 'iamKey']) {
64
+ const { items } = await res.list({ criteria: { [field]: value }, pager: { size: 1 } });
65
+ if (items[0] != null)
66
+ return remember(items[0], value);
67
+ }
68
+ return remember(null, value);
69
+ };
70
+ const service = appendContextual(alias, {
71
+ resolve: async (value) => toRef(await load(value)),
72
+ byId: async (id) => {
73
+ const entity = RECORD_ID.test(id) ? await resource().load(id) : null;
74
+ return toRef(remember(entity, id));
75
+ },
76
+ mintSlug: async () => {
77
+ const res = resource();
78
+ // A word slug is picked from 2^22 pairs, so a collision is rare — but "rare" is not "never"
79
+ // once an installation has many organizations, and the numeric suffix keeps the readable
80
+ // name rather than rerolling into an unrelated one.
81
+ for (let attempt = 1; attempt <= MAX_ENTITY_SLUG_ATTEMPTS; ++attempt) {
82
+ const base = generateWordSlug();
83
+ for (let suffix = 1; suffix <= 3; ++suffix) {
84
+ const candidate = nextSlugCandidate(base, suffix);
85
+ const { items } = await res.list({
86
+ criteria: { $or: [{ slug: candidate }, { formerSlugs: candidate }] },
87
+ pager: { size: 1 },
88
+ });
89
+ if (items.length === 0)
90
+ return candidate;
91
+ }
92
+ }
93
+ throw new SyntaxError('entity:slug-exhausted');
94
+ },
95
+ rename: async (id, slug) => {
96
+ if (!ENTITY_SLUG_PATTERN.test(slug)) {
97
+ throw new SyntaxError(`entity:slug-malformed:${slug}`);
98
+ }
99
+ const res = resource();
100
+ const entity = await res.get(id);
101
+ if (entity.slug === slug) {
102
+ return toRef(entity);
103
+ }
104
+ // A slug another entity has ever answered to cannot be taken: tokens and third-party records
105
+ // quoting it would start resolving to the wrong organization.
106
+ const { items: taken } = await res.list({
107
+ criteria: { $or: [{ slug }, { formerSlugs: slug }] }, pager: { size: 1 },
108
+ });
109
+ if (taken[0] != null && taken[0].id !== id) {
110
+ throw new SyntaxError(`entity:slug-taken:${slug}`);
111
+ }
112
+ forget(entity);
113
+ const formerSlugs = [...new Set([...(entity.formerSlugs ?? []), entity.slug])]
114
+ .filter(former => former !== slug);
115
+ const updated = await res.update({ ...entity, slug, formerSlugs, updatedAt: new Date() });
116
+ return toRef(remember(updated));
117
+ },
118
+ mintName: async (id, key, mint) => {
119
+ const res = resource();
120
+ const entity = await res.get(id);
121
+ const existing = entity.names?.[key];
122
+ // Read-before-mint is what freezes the name: the second caller gets the first caller's
123
+ // answer, so a rename between the two cannot produce two names for one live resource.
124
+ if (existing != null && existing !== '') {
125
+ return existing;
126
+ }
127
+ const minted = mint(toRef(entity));
128
+ forget(entity);
129
+ const updated = await res.update({
130
+ ...entity, names: { ...entity.names, [key]: minted }, updatedAt: new Date(),
131
+ });
132
+ remember(updated);
133
+ return updated.names?.[key] ?? minted;
134
+ },
135
+ });
136
+ return service;
137
+ };
138
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,14 @@
1
- import type { IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource } from './types.js';
1
+ import type { IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource, OrgEntityResource } from './types.js';
2
+ /**
3
+ * The organization-entity registry.
4
+ *
5
+ * Both unique indexes are load-bearing. `slug` is what a rename writes, so the index is the only
6
+ * thing standing between two organizations and the same public name; `iamKey` is quoted by systems
7
+ * that were told it once and never asked again, so a duplicate there would silently hand one
8
+ * organization another's realm. `formerSlugs` is indexed but NOT unique-scoped as a set — a retired
9
+ * slug must stay findable, and uniqueness against it is enforced when a rename is applied.
10
+ */
11
+ export declare const makeOrgEntityResource: (dbAlias?: string) => OrgEntityResource;
2
12
  export declare const makeIdentityAccountResource: (dbAlias?: string) => IdentityAccountResource;
3
13
  export declare const makeIdentityProfileResource: (dbAlias?: string) => IdentityProfileResource;
4
14
  export declare const makeIdentityCredentialsResource: (dbAlias?: string) => IdentityCredentialsResource;
@@ -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,EAAE,MAAM,YAAY,CAAA;AAMtK,eAAO,MAAM,2BAA2B,aAAc,MAAM,KAAG,uBAQ9D,CAAA;AAED,eAAO,MAAM,2BAA2B,aAAc,MAAM,KAAG,uBAe9D,CAAA;AAED,eAAO,MAAM,+BAA+B,aAAc,MAAM,KAAG,2BAOlE,CAAA"}
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,uBAQ9D,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
@@ -1,5 +1,21 @@
1
1
  import { makeMongoResource } from '@owlmeans/mongo-resource';
2
- import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_ACCOUNT_COLLECTION, AUTH_IDENTITY_PROFILE_COLLECTION, AUTH_IDENTITY_CREDENTIALS_COLLECTION } from './consts.js';
2
+ import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_ORG_ENTITY, AUTH_IDENTITY_ACCOUNT_COLLECTION, AUTH_IDENTITY_PROFILE_COLLECTION, AUTH_IDENTITY_CREDENTIALS_COLLECTION, AUTH_IDENTITY_ORG_ENTITY_COLLECTION } from './consts.js';
3
+ /**
4
+ * The organization-entity registry.
5
+ *
6
+ * Both unique indexes are load-bearing. `slug` is what a rename writes, so the index is the only
7
+ * thing standing between two organizations and the same public name; `iamKey` is quoted by systems
8
+ * that were told it once and never asked again, so a duplicate there would silently hand one
9
+ * organization another's realm. `formerSlugs` is indexed but NOT unique-scoped as a set — a retired
10
+ * slug must stay findable, and uniqueness against it is enforced when a rename is applied.
11
+ */
12
+ export const makeOrgEntityResource = (dbAlias) => {
13
+ const resource = makeMongoResource(AUTH_IDENTITY_ORG_ENTITY, dbAlias, undefined, undefined, AUTH_IDENTITY_ORG_ENTITY_COLLECTION);
14
+ resource.index('slug', { slug: 1 }, { unique: true });
15
+ resource.index('iamKey', { iamKey: 1 }, { unique: true });
16
+ resource.index('formerSlugs', { formerSlugs: 1 });
17
+ return resource;
18
+ };
3
19
  export const makeIdentityAccountResource = (dbAlias) => {
4
20
  const resource = makeMongoResource(AUTH_IDENTITY_ACCOUNT, dbAlias, undefined, undefined, AUTH_IDENTITY_ACCOUNT_COLLECTION);
5
21
  resource.index('credential', { credential: 1 }, { unique: true });
@@ -1 +1 @@
1
- {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EACL,qBAAqB,EAAE,qBAAqB,EAAE,yBAAyB,EACvE,gCAAgC,EAAE,gCAAgC,EAAE,oCAAoC,EACzG,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAAgB,EAA2B,EAAE;IACvF,MAAM,QAAQ,GAAG,iBAAiB,CAChC,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,gCAAgC,CACvF,CAAA;IACD,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACjE,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACvE,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAAgB,EAA2B,EAAE;IACvF,MAAM,QAAQ,GAAG,iBAAiB,CAChC,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,gCAAgC,CACvF,CAAA;IACD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAChD,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1E,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,OAAgB,EAA+B,EAAE;IAC/F,MAAM,QAAQ,GAAG,iBAAiB,CAChC,yBAAyB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,oCAAoC,CAC/F,CAAA;IACD,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACnF,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7C,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA"}
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAepJ,eAAO,MAAM,0BAA0B,QAAO,sBA6K7C,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAkBpJ,eAAO,MAAM,0BAA0B,QAAO,sBAyM7C,CAAA"}
package/build/service.js CHANGED
@@ -1,13 +1,28 @@
1
1
  import { appendContextual } from '@owlmeans/context';
2
2
  import { ALL_SCOPES, AuthRole } from '@owlmeans/auth';
3
3
  import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids';
4
- import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js';
4
+ import { ENTITY_RESOLVER } from '@owlmeans/auth-common';
5
+ import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, AUTH_IDENTITY_ORG_ENTITY, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js';
5
6
  const MAX_SLUG_RETRIES = 5;
6
7
  // Stable external login key: "{type}:{service}:{providerSub}"
7
8
  const externalKey = (details) => [details.type, details.service, details.userId].join(EXTERNAL_KEY_DELIMITER);
8
9
  // Login-service credential stored on profile and credentials: "service:{type}:{service}"
9
10
  const loginService = (details) => [LOGIN_SERVICE_PREFIX, details.type, details.service].join(EXTERNAL_KEY_DELIMITER);
10
11
  export const makeIdentityLinkingService = () => {
12
+ /**
13
+ * The wire value for a stored entity id.
14
+ *
15
+ * Records key on the id; everything this service hands back is an auth payload, and payloads
16
+ * carry the slug. An id that no longer resolves yields undefined rather than leaking the raw
17
+ * id onto the wire, where a consumer would mistake it for a slug and compose names from it.
18
+ */
19
+ const slugOf = async (entityId) => {
20
+ if (entityId == null || entityId === '')
21
+ return undefined;
22
+ const ctx = service.ctx;
23
+ const entity = await ctx.service(ENTITY_RESOLVER).byId(entityId);
24
+ return entity?.slug;
25
+ };
11
26
  const service = appendContextual(AUTH_IDENTITY_LINKING, {
12
27
  getLinkedProfile: async (details) => {
13
28
  const ctx = service.ctx;
@@ -31,7 +46,7 @@ export const makeIdentityLinkingService = () => {
31
46
  role: profile.role,
32
47
  userId: profile.userId ?? profile.profileId,
33
48
  profileId: profile.profileId,
34
- entityId: profile.entityId,
49
+ entitySlug: await slugOf(profile.entityId),
35
50
  scopes: profile.scopes,
36
51
  };
37
52
  },
@@ -40,19 +55,31 @@ export const makeIdentityLinkingService = () => {
40
55
  const accountResource = ctx.resource(AUTH_IDENTITY_ACCOUNT);
41
56
  const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
42
57
  const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
43
- // Generate a unique local entity slug stored as the account credential.
58
+ const entityResource = ctx.resource(AUTH_IDENTITY_ORG_ENTITY);
59
+ const resolver = ctx.service(ENTITY_RESOLVER);
60
+ // Every account gets its own organization. The entity record is created FIRST because it
61
+ // owns the two values that outlive everything else here: the id the account and its profiles
62
+ // are keyed by, and the frozen `iamKey` that realms and object names are minted from.
63
+ const entity = await entityResource.create({
64
+ slug: await resolver.mintSlug(),
65
+ formerSlugs: [],
66
+ // Frozen at birth and never recomputed. It cannot be the slug — the slug moves — and it
67
+ // cannot be the record id, which does not exist until this create returns.
68
+ iamKey: createIdOfLength(16, IdStyle.Base58),
69
+ names: {},
70
+ createdAt: new Date(),
71
+ });
72
+ const entityId = entity.id;
44
73
  let accountId = null;
45
- let entityId = null;
46
74
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
47
75
  const credential = createIdOfLength(16, IdStyle.Base58);
48
76
  try {
49
77
  const account = await accountResource.create({
50
78
  credential,
51
79
  name: meta.username,
52
- entityId: credential,
80
+ entityId,
53
81
  });
54
82
  accountId = account.id;
55
- entityId = account.credential;
56
83
  break;
57
84
  }
58
85
  catch (err) {
@@ -61,7 +88,7 @@ export const makeIdentityLinkingService = () => {
61
88
  throw err;
62
89
  }
63
90
  }
64
- if (accountId == null || entityId == null) {
91
+ if (accountId == null) {
65
92
  throw new Error('Failed to generate unique account credential after retries');
66
93
  }
67
94
  const profileId = [details.type, accountId].join(EXTERNAL_KEY_DELIMITER);
@@ -98,7 +125,7 @@ export const makeIdentityLinkingService = () => {
98
125
  role: profile.role,
99
126
  userId: profile.userId ?? profile.profileId,
100
127
  profileId: profile.profileId,
101
- entityId: profile.entityId,
128
+ entitySlug: await slugOf(profile.entityId),
102
129
  scopes: profile.scopes,
103
130
  };
104
131
  },
@@ -124,16 +151,16 @@ export const makeIdentityLinkingService = () => {
124
151
  const ctx = service.ctx;
125
152
  const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
126
153
  const { items: profiles } = await profileResource.list({ criteria: { entityId } });
127
- return profiles.map(p => ({
154
+ return await Promise.all(profiles.map(async (p) => ({
128
155
  id: p.profileId,
129
156
  name: p.name,
130
157
  credential: p.credential,
131
- entityId: p.entityId,
158
+ entitySlug: await slugOf(p.entityId),
132
159
  scopes: p.scopes,
133
160
  groups: p.groups,
134
161
  permissions: p.permissions,
135
162
  attributes: p.attributes,
136
- }));
163
+ })));
137
164
  },
138
165
  getOwnerCredentials: async (userId, entityId, type) => {
139
166
  const ctx = service.ctx;
@@ -160,7 +187,7 @@ export const makeIdentityLinkingService = () => {
160
187
  role: profile.role,
161
188
  userId: profile.userId ?? profile.profileId,
162
189
  profileId: profile.profileId,
163
- entityId: profile.entityId,
190
+ entitySlug: await slugOf(profile.entityId),
164
191
  scopes: profile.scopes,
165
192
  challenge: cred.challenge,
166
193
  credential: cred.credential,
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -6,22 +6,28 @@ import type { AuthCredentials, AuthPayload, AuthRole, Profile } from '@owlmeans/
6
6
  import type { ProviderProfileDetails } from '@owlmeans/oidc';
7
7
  /**
8
8
  * Local account record — one per user.
9
- * id is the Mongo resource id; credential is the stable unique local entity slug.
9
+ *
10
+ * `id` is the Mongo resource id; `credential` is the account's stable unique key. `entityId`
11
+ * (inherited from Profile below) holds the organization entity's record id — never its slug.
10
12
  */
11
- export interface IdentityAccount extends Profile, ResourceRecord {
13
+ export interface IdentityAccount extends Omit<Profile, 'entitySlug'>, ResourceRecord {
12
14
  id: string;
13
15
  credential: string;
16
+ /** Stable organization-entity id. Renaming the organization does not touch this. */
17
+ entityId?: string;
14
18
  }
15
19
  /**
16
20
  * Local profile record — ties a user to an entity with a role.
17
21
  * Mirrors IAMProfile from @owlmeans/auth backend.
18
22
  */
19
- export interface IdentityProfile extends Profile, ResourceRecord {
23
+ export interface IdentityProfile extends Omit<Profile, 'entitySlug'>, ResourceRecord {
20
24
  id: string;
21
25
  profileId: string;
22
26
  userId?: string;
23
27
  role: AuthRole;
24
28
  expiresAt?: Date;
29
+ /** Stable organization-entity id — the value every per-organization query filters on. */
30
+ entityId?: string;
25
31
  }
26
32
  /**
27
33
  * Provider credentials record — one per provider link per profile.
@@ -32,6 +38,27 @@ export interface IdentityProfile extends Profile, ResourceRecord {
32
38
  export interface IdentityCredentials extends AuthCredentials, ResourceRecord {
33
39
  profileId: string;
34
40
  }
41
+ /**
42
+ * An organization entity — the record every per-organization store keys on.
43
+ *
44
+ * Its `id` is the stable entity id. The slug is the value users, URLs and tokens see, and it may
45
+ * move; nothing here except `slug` itself changes when it does, which is the entire point of the
46
+ * record existing.
47
+ */
48
+ export interface OrgEntity extends ResourceRecord {
49
+ id?: string;
50
+ slug: string;
51
+ formerSlugs?: string[];
52
+ iamKey: string;
53
+ /**
54
+ * Names this organization is known by in systems that cannot rename: a Kubernetes namespace, a
55
+ * storage prefix, an IAM realm. Minted once through `mintName` and read back forever after.
56
+ */
57
+ names?: Record<string, string>;
58
+ createdAt: Date;
59
+ updatedAt?: Date;
60
+ }
61
+ export type OrgEntityResource = MongoResource<OrgEntity>;
35
62
  export type IdentityAccountResource = MongoResource<IdentityAccount>;
36
63
  export type IdentityProfileResource = MongoResource<IdentityProfile>;
37
64
  export type IdentityCredentialsResource = MongoResource<IdentityCredentials>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAE5D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,OAAO,EAAE,cAAc;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,OAAO,EAAE,cAAc;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe,EAAE,cAAc;IAC1E,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AACpE,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AACpE,MAAM,MAAM,2BAA2B,GAAG,aAAa,CAAC,mBAAmB,CAAC,CAAA;AAE5E;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,gBAAgB,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAClF,WAAW,EAAE,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACzF,eAAe,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1E,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC1D,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAA;CAChH;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;CACnD;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CACnG;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
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.0",
3
+ "version": "0.1.18-rc.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,20 +22,21 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/auth": "^0.1.18-rc.0",
26
- "@owlmeans/basic-ids": "^0.1.18-rc.0",
27
- "@owlmeans/context": "^0.1.18-rc.0",
28
- "@owlmeans/mongo-resource": "^0.1.18-rc.0",
29
- "@owlmeans/oidc": "^0.1.18-rc.0",
30
- "@owlmeans/resource": "^0.1.18-rc.0",
31
- "@owlmeans/server-context": "^0.1.18-rc.0"
25
+ "@owlmeans/auth": "^0.1.18-rc.7",
26
+ "@owlmeans/auth-common": "^0.1.18-rc.9",
27
+ "@owlmeans/basic-ids": "^0.1.18-rc.7",
28
+ "@owlmeans/context": "^0.1.18-rc.6",
29
+ "@owlmeans/mongo-resource": "^0.1.18-rc.8",
30
+ "@owlmeans/oidc": "^0.1.18-rc.9",
31
+ "@owlmeans/resource": "^0.1.18-rc.7",
32
+ "@owlmeans/server-context": "^0.1.18-rc.8"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "mongodb": "*"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@owlmeans/dep-config": "workspace:*",
38
- "@types/bun": "^1.3.14",
39
+ "@types/bun": "^1.4.0",
39
40
  "nodemon": "^3.1.14",
40
41
  "typescript": "^7.0.2"
41
42
  },
package/src/consts.ts CHANGED
@@ -7,6 +7,7 @@ export const AUTH_IDENTITY_ACCOUNT = 'auth-identity:account'
7
7
  export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile'
8
8
  export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials'
9
9
  export const AUTH_IDENTITY_LINKING = 'auth-identity:linking'
10
+ export const AUTH_IDENTITY_ORG_ENTITY = 'auth-identity:org-entity'
10
11
 
11
12
  // Colon-free Mongo collection base names. The final collection name is
12
13
  // `${resourcePrefix}${collection}` — e.g. with the project prefix 'viam-' this yields
@@ -15,11 +16,16 @@ export const AUTH_IDENTITY_LINKING = 'auth-identity:linking'
15
16
  export const AUTH_IDENTITY_ACCOUNT_COLLECTION = 'account'
16
17
  export const AUTH_IDENTITY_PROFILE_COLLECTION = 'profile'
17
18
  export const AUTH_IDENTITY_CREDENTIALS_COLLECTION = 'credentials'
19
+ export const AUTH_IDENTITY_ORG_ENTITY_COLLECTION = 'org-entity'
18
20
 
19
21
  // Suggested db-config alias for a dedicated config whose resourcePrefix scopes the
20
22
  // collection prefix to identity collections only (not the whole database).
21
23
  export const AUTH_IDENTITY_DB_ALIAS = 'auth-identity'
22
24
 
25
+ // How many slugs to try before giving up on minting a free one. Collisions are settled against a
26
+ // unique index, so this bounds a retry loop rather than a probability.
27
+ export const MAX_ENTITY_SLUG_ATTEMPTS = 8
28
+
23
29
  // Helper prefixes for stable key derivation
24
30
  export const LOGIN_SERVICE_PREFIX = 'service'
25
31
  export const EXTERNAL_KEY_DELIMITER = ':'
package/src/helper.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { BasicContext } from '@owlmeans/context'
2
- import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource } from './resource.js'
2
+ import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource, makeOrgEntityResource } from './resource.js'
3
3
  import { makeIdentityLinkingService } from './service.js'
4
+ import { makeEntityResolverService } from './resolver.js'
4
5
 
5
6
  export const appendAuthIdentityResources = (
6
7
  context: BasicContext<any>,
@@ -9,5 +10,10 @@ export const appendAuthIdentityResources = (
9
10
  context.registerResource(makeIdentityAccountResource(dbAlias))
10
11
  context.registerResource(makeIdentityProfileResource(dbAlias))
11
12
  context.registerResource(makeIdentityCredentialsResource(dbAlias))
13
+ context.registerResource(makeOrgEntityResource(dbAlias))
12
14
  context.registerService(makeIdentityLinkingService())
15
+ // Registering the resolver is what tells the server boundary that this deployment HAS
16
+ // organizations: without it `request.entity` stays undefined and every consumer falls back to
17
+ // treating the token's slug as the only entity value there is.
18
+ context.registerService(makeEntityResolverService())
13
19
  }
package/src/index.ts CHANGED
@@ -2,5 +2,6 @@ export * from './consts.js'
2
2
  export type * from './types.js'
3
3
  export * from './resource.js'
4
4
  export * from './service.js'
5
+ export * from './resolver.js'
5
6
  export * from './helper.js'
6
7
  export * from './iam.js'
@@ -0,0 +1,171 @@
1
+ import { appendContextual } from '@owlmeans/context'
2
+ import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
3
+ import type { EntityResolverService, OrgEntityRef } from '@owlmeans/auth-common'
4
+ import { ENTITY_RESOLVER, ENTITY_SLUG_PATTERN } from '@owlmeans/auth-common'
5
+ import { generateWordSlug, nextSlugCandidate } from '@owlmeans/basic-ids'
6
+ import type { OrgEntity, OrgEntityResource } from './types.js'
7
+ import { AUTH_IDENTITY_ORG_ENTITY, MAX_ENTITY_SLUG_ATTEMPTS } from './consts.js'
8
+
9
+ type Context = ServerContext<ServerConfig>
10
+
11
+ /**
12
+ * How long a resolution is trusted without re-reading the registry.
13
+ *
14
+ * Every authenticated request resolves an entity, so an uncached resolver would put a database
15
+ * round-trip in front of the whole API. The cost of the cache is a rename taking up to this long
16
+ * to be seen by other replicas — which is survivable precisely because the old slug keeps
17
+ * resolving through `formerSlugs` rather than failing.
18
+ */
19
+ const CACHE_TTL = 30_000
20
+
21
+ /** What a stored record id looks like. Anything else can only be a slug or a frozen key. */
22
+ const RECORD_ID = /^[0-9a-f]{24}$/i
23
+
24
+ /** A stored record as the contract exposes it. Persisted records always carry an id. */
25
+ const toRef = (entity: OrgEntity | null): OrgEntityRef | null =>
26
+ entity == null ? null : {
27
+ id: entity.id!, slug: entity.slug, formerSlugs: entity.formerSlugs, iamKey: entity.iamKey,
28
+ }
29
+
30
+ interface CacheEntry {
31
+ entity: OrgEntity | null
32
+ at: number
33
+ }
34
+
35
+ export const makeEntityResolverService = (
36
+ alias: string = ENTITY_RESOLVER
37
+ ): EntityResolverService => {
38
+ // Keyed by every name a caller might arrive with — id, slug, former slug, iamKey — so a hit
39
+ // costs one lookup regardless of which one it was.
40
+ const cache = new Map<string, CacheEntry>()
41
+ const now = () => Date.now()
42
+
43
+ const remember = (entity: OrgEntity | null, ...keys: string[]): OrgEntity | null => {
44
+ const at = now()
45
+ for (const key of keys) {
46
+ if (key !== '') cache.set(key, { entity, at })
47
+ }
48
+ if (entity != null) {
49
+ cache.set(entity.id!, { entity, at })
50
+ cache.set(entity.slug, { entity, at })
51
+ cache.set(entity.iamKey, { entity, at })
52
+ for (const former of entity.formerSlugs ?? []) cache.set(former, { entity, at })
53
+ }
54
+
55
+ return entity
56
+ }
57
+
58
+ const forget = (entity: OrgEntity) => {
59
+ for (const key of [entity.id, entity.slug, entity.iamKey, ...(entity.formerSlugs ?? [])]) {
60
+ if (key != null) cache.delete(key)
61
+ }
62
+ }
63
+
64
+ const resource = (): OrgEntityResource =>
65
+ (service.ctx as Context).resource<OrgEntityResource>(AUTH_IDENTITY_ORG_ENTITY)
66
+
67
+ const load = async (value: string): Promise<OrgEntity | null> => {
68
+ const cached = cache.get(value)
69
+ if (cached != null && now() - cached.at < CACHE_TTL) {
70
+ return cached.entity
71
+ }
72
+
73
+ const res = resource()
74
+ // Id first: an id is authoritative, so a slug that happens to look like some other entity's
75
+ // id can never shadow it. Then the current slug, then the names it has retired, then the
76
+ // frozen key external systems still quote.
77
+ //
78
+ // The shape test is not an optimisation. `load` reads its argument as a record id, and the
79
+ // store turns that into an ObjectId unconditionally — so handing it a slug throws a driver
80
+ // error out of what is meant to be a lookup that simply misses.
81
+ const byId = RECORD_ID.test(value) ? await res.load(value) : null
82
+ if (byId != null) return remember(byId, value)
83
+
84
+ for (const field of ['slug', 'formerSlugs', 'iamKey'] as const) {
85
+ const { items } = await res.list({ criteria: { [field]: value } as never, pager: { size: 1 } })
86
+ if (items[0] != null) return remember(items[0], value)
87
+ }
88
+
89
+ return remember(null, value)
90
+ }
91
+
92
+ const service: EntityResolverService = appendContextual<EntityResolverService>(alias, {
93
+ resolve: async value => toRef(await load(value)),
94
+
95
+ byId: async id => {
96
+ const entity = RECORD_ID.test(id) ? await resource().load(id) : null
97
+
98
+ return toRef(remember(entity, id))
99
+ },
100
+
101
+ mintSlug: async () => {
102
+ const res = resource()
103
+ // A word slug is picked from 2^22 pairs, so a collision is rare — but "rare" is not "never"
104
+ // once an installation has many organizations, and the numeric suffix keeps the readable
105
+ // name rather than rerolling into an unrelated one.
106
+ for (let attempt = 1; attempt <= MAX_ENTITY_SLUG_ATTEMPTS; ++attempt) {
107
+ const base = generateWordSlug()
108
+ for (let suffix = 1; suffix <= 3; ++suffix) {
109
+ const candidate = nextSlugCandidate(base, suffix)
110
+ const { items } = await res.list({
111
+ criteria: { $or: [{ slug: candidate }, { formerSlugs: candidate }] } as never,
112
+ pager: { size: 1 },
113
+ })
114
+ if (items.length === 0) return candidate
115
+ }
116
+ }
117
+
118
+ throw new SyntaxError('entity:slug-exhausted')
119
+ },
120
+
121
+ rename: async (id, slug) => {
122
+ if (!ENTITY_SLUG_PATTERN.test(slug)) {
123
+ throw new SyntaxError(`entity:slug-malformed:${slug}`)
124
+ }
125
+
126
+ const res = resource()
127
+ const entity = await res.get(id)
128
+ if (entity.slug === slug) {
129
+ return toRef(entity)!
130
+ }
131
+
132
+ // A slug another entity has ever answered to cannot be taken: tokens and third-party records
133
+ // quoting it would start resolving to the wrong organization.
134
+ const { items: taken } = await res.list({
135
+ criteria: { $or: [{ slug }, { formerSlugs: slug }] } as never, pager: { size: 1 },
136
+ })
137
+ if (taken[0] != null && taken[0].id !== id) {
138
+ throw new SyntaxError(`entity:slug-taken:${slug}`)
139
+ }
140
+
141
+ forget(entity)
142
+ const formerSlugs = [...new Set([...(entity.formerSlugs ?? []), entity.slug])]
143
+ .filter(former => former !== slug)
144
+ const updated = await res.update({ ...entity, slug, formerSlugs, updatedAt: new Date() })
145
+
146
+ return toRef(remember(updated))!
147
+ },
148
+
149
+ mintName: async (id, key, mint) => {
150
+ const res = resource()
151
+ const entity = await res.get(id)
152
+ const existing = entity.names?.[key]
153
+ // Read-before-mint is what freezes the name: the second caller gets the first caller's
154
+ // answer, so a rename between the two cannot produce two names for one live resource.
155
+ if (existing != null && existing !== '') {
156
+ return existing
157
+ }
158
+
159
+ const minted = mint(toRef(entity)!)
160
+ forget(entity)
161
+ const updated = await res.update({
162
+ ...entity, names: { ...entity.names, [key]: minted }, updatedAt: new Date(),
163
+ })
164
+ remember(updated)
165
+
166
+ return updated.names?.[key] ?? minted
167
+ },
168
+ })
169
+
170
+ return service
171
+ }
package/src/resource.ts CHANGED
@@ -1,10 +1,30 @@
1
1
  import { makeMongoResource } from '@owlmeans/mongo-resource'
2
- import type { IdentityAccount, IdentityAccountResource, IdentityProfile, IdentityProfileResource, IdentityCredentials, IdentityCredentialsResource } from './types.js'
2
+ import type { IdentityAccount, IdentityAccountResource, IdentityProfile, IdentityProfileResource, IdentityCredentials, IdentityCredentialsResource, OrgEntity, OrgEntityResource } from './types.js'
3
3
  import {
4
- AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS,
5
- AUTH_IDENTITY_ACCOUNT_COLLECTION, AUTH_IDENTITY_PROFILE_COLLECTION, AUTH_IDENTITY_CREDENTIALS_COLLECTION
4
+ AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_ORG_ENTITY,
5
+ AUTH_IDENTITY_ACCOUNT_COLLECTION, AUTH_IDENTITY_PROFILE_COLLECTION, AUTH_IDENTITY_CREDENTIALS_COLLECTION,
6
+ AUTH_IDENTITY_ORG_ENTITY_COLLECTION
6
7
  } from './consts.js'
7
8
 
9
+ /**
10
+ * The organization-entity registry.
11
+ *
12
+ * Both unique indexes are load-bearing. `slug` is what a rename writes, so the index is the only
13
+ * thing standing between two organizations and the same public name; `iamKey` is quoted by systems
14
+ * that were told it once and never asked again, so a duplicate there would silently hand one
15
+ * organization another's realm. `formerSlugs` is indexed but NOT unique-scoped as a set — a retired
16
+ * slug must stay findable, and uniqueness against it is enforced when a rename is applied.
17
+ */
18
+ export const makeOrgEntityResource = (dbAlias?: string): OrgEntityResource => {
19
+ const resource = makeMongoResource<OrgEntity, OrgEntityResource>(
20
+ AUTH_IDENTITY_ORG_ENTITY, dbAlias, undefined, undefined, AUTH_IDENTITY_ORG_ENTITY_COLLECTION
21
+ )
22
+ resource.index('slug', { slug: 1 }, { unique: true })
23
+ resource.index('iamKey', { iamKey: 1 }, { unique: true })
24
+ resource.index('formerSlugs', { formerSlugs: 1 })
25
+ return resource
26
+ }
27
+
8
28
  export const makeIdentityAccountResource = (dbAlias?: string): IdentityAccountResource => {
9
29
  const resource = makeMongoResource<IdentityAccount, IdentityAccountResource>(
10
30
  AUTH_IDENTITY_ACCOUNT, dbAlias, undefined, undefined, AUTH_IDENTITY_ACCOUNT_COLLECTION
package/src/service.ts CHANGED
@@ -5,7 +5,10 @@ import { ALL_SCOPES, AuthRole } from '@owlmeans/auth'
5
5
  import type { ProviderProfileDetails } from '@owlmeans/oidc'
6
6
  import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids'
7
7
  import type { AccountMeta, IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource, IdentityLinkingService } from './types.js'
8
- import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js'
8
+ import type { OrgEntity, OrgEntityResource } from './types.js'
9
+ import type { EntityResolverService } from '@owlmeans/auth-common'
10
+ import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
11
+ import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, AUTH_IDENTITY_ORG_ENTITY, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js'
9
12
 
10
13
  type Context = ServerContext<ServerConfig>
11
14
 
@@ -20,6 +23,21 @@ const loginService = (details: ProviderProfileDetails): string =>
20
23
  [LOGIN_SERVICE_PREFIX, details.type, details.service].join(EXTERNAL_KEY_DELIMITER)
21
24
 
22
25
  export const makeIdentityLinkingService = (): IdentityLinkingService => {
26
+ /**
27
+ * The wire value for a stored entity id.
28
+ *
29
+ * Records key on the id; everything this service hands back is an auth payload, and payloads
30
+ * carry the slug. An id that no longer resolves yields undefined rather than leaking the raw
31
+ * id onto the wire, where a consumer would mistake it for a slug and compose names from it.
32
+ */
33
+ const slugOf = async (entityId?: string): Promise<string | undefined> => {
34
+ if (entityId == null || entityId === '') return undefined
35
+ const ctx = service.ctx as Context
36
+ const entity = await ctx.service<EntityResolverService>(ENTITY_RESOLVER).byId(entityId)
37
+
38
+ return entity?.slug
39
+ }
40
+
23
41
  const service: IdentityLinkingService = appendContextual<IdentityLinkingService>(AUTH_IDENTITY_LINKING, {
24
42
  getLinkedProfile: async (details: ProviderProfileDetails): Promise<AuthPayload | null> => {
25
43
  const ctx = service.ctx as Context
@@ -44,7 +62,7 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
44
62
  role: profile.role,
45
63
  userId: profile.userId ?? profile.profileId,
46
64
  profileId: profile.profileId,
47
- entityId: profile.entityId,
65
+ entitySlug: await slugOf(profile.entityId),
48
66
  scopes: profile.scopes,
49
67
  }
50
68
  },
@@ -54,20 +72,33 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
54
72
  const accountResource = ctx.resource<IdentityAccountResource>(AUTH_IDENTITY_ACCOUNT)
55
73
  const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
56
74
  const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
75
+ const entityResource = ctx.resource<OrgEntityResource>(AUTH_IDENTITY_ORG_ENTITY)
76
+ const resolver = ctx.service<EntityResolverService>(ENTITY_RESOLVER)
77
+
78
+ // Every account gets its own organization. The entity record is created FIRST because it
79
+ // owns the two values that outlive everything else here: the id the account and its profiles
80
+ // are keyed by, and the frozen `iamKey` that realms and object names are minted from.
81
+ const entity: OrgEntity = await entityResource.create({
82
+ slug: await resolver.mintSlug(),
83
+ formerSlugs: [],
84
+ // Frozen at birth and never recomputed. It cannot be the slug — the slug moves — and it
85
+ // cannot be the record id, which does not exist until this create returns.
86
+ iamKey: createIdOfLength(16, IdStyle.Base58),
87
+ names: {},
88
+ createdAt: new Date(),
89
+ })
90
+ const entityId = entity.id!
57
91
 
58
- // Generate a unique local entity slug stored as the account credential.
59
92
  let accountId: string | null = null
60
- let entityId: string | null = null
61
93
  for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
62
94
  const credential = createIdOfLength(16, IdStyle.Base58)
63
95
  try {
64
96
  const account = await accountResource.create({
65
97
  credential,
66
98
  name: meta.username,
67
- entityId: credential,
99
+ entityId,
68
100
  })
69
101
  accountId = account.id
70
- entityId = account.credential
71
102
  break
72
103
  } catch (err: any) {
73
104
  if (err?.code === 11000 || err?.message?.includes('duplicate')) continue
@@ -75,7 +106,7 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
75
106
  }
76
107
  }
77
108
 
78
- if (accountId == null || entityId == null) {
109
+ if (accountId == null) {
79
110
  throw new Error('Failed to generate unique account credential after retries')
80
111
  }
81
112
 
@@ -114,7 +145,7 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
114
145
  role: profile.role,
115
146
  userId: profile.userId ?? profile.profileId,
116
147
  profileId: profile.profileId,
117
- entityId: profile.entityId,
148
+ entitySlug: await slugOf(profile.entityId),
118
149
  scopes: profile.scopes,
119
150
  }
120
151
  },
@@ -145,16 +176,16 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
145
176
  const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
146
177
  const { items: profiles } = await profileResource.list({ criteria: { entityId } })
147
178
 
148
- return profiles.map(p => ({
179
+ return await Promise.all(profiles.map(async p => ({
149
180
  id: p.profileId,
150
181
  name: p.name,
151
182
  credential: p.credential,
152
- entityId: p.entityId,
183
+ entitySlug: await slugOf(p.entityId),
153
184
  scopes: p.scopes,
154
185
  groups: p.groups,
155
186
  permissions: p.permissions,
156
187
  attributes: p.attributes,
157
- }))
188
+ })))
158
189
  },
159
190
 
160
191
  getOwnerCredentials: async (userId: string, entityId?: string, type?: string): Promise<AuthCredentials | undefined> => {
@@ -182,7 +213,7 @@ export const makeIdentityLinkingService = (): IdentityLinkingService => {
182
213
  role: profile.role,
183
214
  userId: profile.userId ?? profile.profileId,
184
215
  profileId: profile.profileId,
185
- entityId: profile.entityId,
216
+ entitySlug: await slugOf(profile.entityId),
186
217
  scopes: profile.scopes,
187
218
  challenge: cred.challenge,
188
219
  credential: cred.credential,
package/src/types.ts CHANGED
@@ -7,23 +7,29 @@ import type { ProviderProfileDetails } from '@owlmeans/oidc'
7
7
 
8
8
  /**
9
9
  * Local account record — one per user.
10
- * id is the Mongo resource id; credential is the stable unique local entity slug.
10
+ *
11
+ * `id` is the Mongo resource id; `credential` is the account's stable unique key. `entityId`
12
+ * (inherited from Profile below) holds the organization entity's record id — never its slug.
11
13
  */
12
- export interface IdentityAccount extends Profile, ResourceRecord {
14
+ export interface IdentityAccount extends Omit<Profile, 'entitySlug'>, ResourceRecord {
13
15
  id: string
14
16
  credential: string
17
+ /** Stable organization-entity id. Renaming the organization does not touch this. */
18
+ entityId?: string
15
19
  }
16
20
 
17
21
  /**
18
22
  * Local profile record — ties a user to an entity with a role.
19
23
  * Mirrors IAMProfile from @owlmeans/auth backend.
20
24
  */
21
- export interface IdentityProfile extends Profile, ResourceRecord {
25
+ export interface IdentityProfile extends Omit<Profile, 'entitySlug'>, ResourceRecord {
22
26
  id: string
23
27
  profileId: string
24
28
  userId?: string
25
29
  role: AuthRole
26
30
  expiresAt?: Date
31
+ /** Stable organization-entity id — the value every per-organization query filters on. */
32
+ entityId?: string
27
33
  }
28
34
 
29
35
  /**
@@ -36,6 +42,29 @@ export interface IdentityCredentials extends AuthCredentials, ResourceRecord {
36
42
  profileId: string
37
43
  }
38
44
 
45
+ /**
46
+ * An organization entity — the record every per-organization store keys on.
47
+ *
48
+ * Its `id` is the stable entity id. The slug is the value users, URLs and tokens see, and it may
49
+ * move; nothing here except `slug` itself changes when it does, which is the entire point of the
50
+ * record existing.
51
+ */
52
+ export interface OrgEntity extends ResourceRecord {
53
+ id?: string
54
+ slug: string
55
+ formerSlugs?: string[]
56
+ iamKey: string
57
+ /**
58
+ * Names this organization is known by in systems that cannot rename: a Kubernetes namespace, a
59
+ * storage prefix, an IAM realm. Minted once through `mintName` and read back forever after.
60
+ */
61
+ names?: Record<string, string>
62
+ createdAt: Date
63
+ updatedAt?: Date
64
+ }
65
+
66
+ export type OrgEntityResource = MongoResource<OrgEntity>
67
+
39
68
  export type IdentityAccountResource = MongoResource<IdentityAccount>
40
69
  export type IdentityProfileResource = MongoResource<IdentityProfile>
41
70
  export type IdentityCredentialsResource = MongoResource<IdentityCredentials>