@spotto/contract 1.0.69-alpha.24 → 1.0.69-alpha.26

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.
@@ -14,6 +14,34 @@ export declare type UnknownRoleAction<TRoleId = string> = {
14
14
  type: 'defaultRole';
15
15
  roleId: TRoleId;
16
16
  };
17
+ /**
18
+ * One row in the org's role-mapping list. At sign-in the IdP-emitted claim
19
+ * (captured into `custom:g2g_role` via the per-org Cognito IdP
20
+ * `AttributeMapping`) is normalised to `string[]` and walked against this
21
+ * list in order; the first entry whose `propertyValue` appears in the
22
+ * normalised values assigns its `roleId`.
23
+ *
24
+ * Generic over the identifier type so the same shape covers both the wire
25
+ * layer (string IDs) and the Mongo storage layer (ObjectId).
26
+ */
27
+ export interface RoleMapping<TId = string> {
28
+ _id: TId;
29
+ /**
30
+ * Value to look for in the IdP-emitted claim. Compared with `===` against
31
+ * each entry of the normalised `string[]` — case-sensitive, exact match.
32
+ */
33
+ propertyValue: string;
34
+ /** Spotto role to assign on match. */
35
+ roleId: TId;
36
+ }
37
+ /**
38
+ * Wire shape for creating / replacing mappings. `_id` is server-assigned on
39
+ * each write, so it's not part of the input.
40
+ */
41
+ export interface RoleMappingInput {
42
+ propertyValue: string;
43
+ roleId: string;
44
+ }
17
45
  export interface SsoEnabledBase<TRoleId = string> {
18
46
  enabled: true;
19
47
  /** Email domains owned by this org. One domain → one org (enforced at write time). */
@@ -26,6 +54,18 @@ export interface SsoEnabledBase<TRoleId = string> {
26
54
  inactivityTimeoutMinutes?: number;
27
55
  /** What to do when the role claim is missing or its value isn't a known Spotto role. */
28
56
  unknownRoleAction: UnknownRoleAction<TRoleId>;
57
+ /**
58
+ * IdP-side claim name that feeds the Cognito `custom:g2g_role` attribute.
59
+ * Pushed into the per-org Cognito IdP's `AttributeMapping` at onboard and
60
+ * any time it's updated. Examples: `g2g_role`, `roles`, `groups`, or a
61
+ * SAML AttributeName.
62
+ */
63
+ idpRoleClaim: string;
64
+ /**
65
+ * Org-scoped role-mapping list. Empty means every federated sign-in falls
66
+ * through to `unknownRoleAction`. See `RoleMapping`.
67
+ */
68
+ roleMappings: RoleMapping<TRoleId>[];
29
69
  }
30
70
  export interface SsoEnabledOidc<TRoleId = string> extends SsoEnabledBase<TRoleId> {
31
71
  mode: 'oidc';
@@ -1,12 +1,22 @@
1
1
  import { SsoConfig } from '../../organisations/sso';
2
+ import { SamlIdpSetup } from '../idp-setup';
2
3
  /**
3
- * Response shape for `GET /sso/orgs/:id`. Always returns a discriminated
4
- * `SsoConfig` — for orgs without SSO, the response is `{ enabled: false }`
5
- * rather than 404, so the FE can drive its UI from the discriminator
6
- * without separate "exists" handling.
4
+ * Response shape for `GET /sso/orgs/:id`.
5
+ *
6
+ * `config` is the persisted SSO config always a discriminated `SsoConfig`,
7
+ * with `{ enabled: false }` for orgs that don't have SSO (rather than 404),
8
+ * so the FE can drive its UI from the discriminator without separate
9
+ * "exists" handling.
10
+ *
11
+ * `samlSetup` is populated only when `config.mode === 'saml'`. Carries the
12
+ * values the operator needs to paste into their IdP's SAML config dialog.
13
+ * Absent for OIDC and disabled orgs.
7
14
  *
8
15
  * Scoped to super-users only (via `sso:admin` + cross-account ancestry).
9
16
  * The customer-facing `GET /organisations/:id` deliberately does NOT
10
17
  * include this — SSO config is super-user-only data in the short term.
11
18
  */
12
- export declare type GetSsoOrgResponse = SsoConfig;
19
+ export interface GetSsoOrgResponse {
20
+ config: SsoConfig;
21
+ samlSetup?: SamlIdpSetup;
22
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * IdP-side configuration values an operator needs to plug into their IdP
3
+ * (e.g. Auth0's SAML2 Web App "Settings" JSON) to complete the SSO setup.
4
+ *
5
+ * Returned on the onboard and GET-org-SSO responses so the super-user UI
6
+ * can render copy-paste blocks. Generic-shaped — the FE renders them in
7
+ * the IdP-specific format the operator's tool expects (Auth0, Entra,
8
+ * Okta, etc., all take similar values in slightly different containers).
9
+ *
10
+ * The `acsUrl` (where the IdP POSTs the SAML response) is not in this
11
+ * payload because the FE already knows its Cognito hosted UI domain and
12
+ * can compose `${domain}/saml2/idpresponse` itself — keeps the backend
13
+ * free of an extra hosted-UI-domain config knob.
14
+ */
15
+ export interface SamlIdpSetup {
16
+ /**
17
+ * The Cognito SP audience URN. For Auth0 SAML2 Web App, paste into the
18
+ * "audience" field. For Entra / Okta, the equivalent "Entity ID" /
19
+ * "Audience" field.
20
+ */
21
+ audience: string;
22
+ /**
23
+ * SAML attribute name mappings. Key is the Cognito-side user attribute
24
+ * name; value is the SAML attribute Name the IdP must emit. Auth0
25
+ * SAML2 Web App's "mappings" field takes this shape directly.
26
+ */
27
+ mappings: Record<string, string>;
28
+ /**
29
+ * NameID format Cognito expects from the IdP.
30
+ */
31
+ nameIdentifierFormat: string;
32
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=idp-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idp-setup.js","sourceRoot":"","sources":["../../src/sso/idp-setup.ts"],"names":[],"mappings":""}
@@ -1,5 +1,7 @@
1
1
  export * from './discover';
2
2
  export * from './get';
3
+ export * from './idp-setup';
3
4
  export * from './onboard';
4
5
  export * from './offboard';
5
6
  export * from './revert';
7
+ export * from './sso-mappings';
package/dist/sso/index.js CHANGED
@@ -16,7 +16,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./discover"), exports);
18
18
  __exportStar(require("./get"), exports);
19
+ __exportStar(require("./idp-setup"), exports);
19
20
  __exportStar(require("./onboard"), exports);
20
21
  __exportStar(require("./offboard"), exports);
21
22
  __exportStar(require("./revert"), exports);
23
+ __exportStar(require("./sso-mappings"), exports);
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sso/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,wCAAqB;AACrB,4CAAyB;AACzB,6CAA0B;AAC1B,2CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sso/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,wCAAqB;AACrB,8CAA2B;AAC3B,4CAAyB;AACzB,6CAA0B;AAC1B,2CAAwB;AACxB,iDAA8B"}
@@ -1,3 +1,4 @@
1
+ import { RoleMappingInput } from '../../organisations/sso';
1
2
  /**
2
3
  * Wire shape for `POST /sso/orgs/:id/onboard`. Operator-supplied fields only.
3
4
  *
@@ -7,9 +8,10 @@
7
8
  * the org's `_id` (`buildSsoProviderName` → `sso-{orgId}`).
8
9
  * - `cognitoClientId` — orchestrator returns the Client ID it created.
9
10
  *
10
- * The IdP-side role claim name is also NOT a wire input: customers must
11
- * configure their IdP to emit a claim named `g2g_role`
12
- * (see `DEFAULT_IDP_ROLE_CLAIM`).
11
+ * `idpRoleClaim` IS a wire input it names the IdP claim that the per-org
12
+ * Cognito `AttributeMapping` should wire into `custom:g2g_role`. Customers
13
+ * pick whatever their IdP naturally emits (e.g. `roles`, `groups`); Spotto
14
+ * maps the captured values to roles via `roleMappings`.
13
15
  */
14
16
  interface OnboardSsoOrgWireBase {
15
17
  emailDomains: string[];
@@ -22,6 +24,13 @@ interface OnboardSsoOrgWireBase {
22
24
  };
23
25
  callbackUrls: string[];
24
26
  logoutUrls: string[];
27
+ idpRoleClaim: string;
28
+ /**
29
+ * Initial mappings. Optional — when absent or empty, every federated
30
+ * sign-in falls through to `unknownRoleAction` until the admin populates
31
+ * mappings via `PUT /sso/orgs/:id/sso-mappings`.
32
+ */
33
+ roleMappings?: RoleMappingInput[];
25
34
  }
26
35
  export interface OnboardSsoOrgOidcRequest extends OnboardSsoOrgWireBase {
27
36
  mode: 'oidc';
@@ -1,16 +1,3 @@
1
1
  "use strict";
2
- /**
3
- * Wire shape for `POST /sso/orgs/:id/onboard`. Operator-supplied fields only.
4
- *
5
- * Two persisted-shape fields are NOT wire inputs:
6
- *
7
- * - `cognitoProviderName` — orchestrator computes it deterministically from
8
- * the org's `_id` (`buildSsoProviderName` → `sso-{orgId}`).
9
- * - `cognitoClientId` — orchestrator returns the Client ID it created.
10
- *
11
- * The IdP-side role claim name is also NOT a wire input: customers must
12
- * configure their IdP to emit a claim named `g2g_role`
13
- * (see `DEFAULT_IDP_ROLE_CLAIM`).
14
- */
15
2
  Object.defineProperty(exports, "__esModule", { value: true });
16
3
  //# sourceMappingURL=request.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request.js","sourceRoot":"","sources":["../../../src/sso/onboard/request.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG"}
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../../src/sso/onboard/request.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
+ import { SamlIdpSetup } from '../idp-setup';
1
2
  /**
2
3
  * Response shape for `POST /sso/orgs/:id/onboard`. Returned on the success
3
4
  * path; on failure the standard ValidationError / DatabaseError envelope
@@ -10,9 +11,15 @@
10
11
  *
11
12
  * `correlationId` threads through every audit log entry for this onboard
12
13
  * — useful to surface in the UI for ops to copy-paste when investigating.
14
+ *
15
+ * `samlSetup` is populated only on SAML onboards. It carries the values
16
+ * the operator needs to plug into their IdP's SAML config dialog (Auth0
17
+ * SAML2 Web App, Entra Enterprise Application, Okta SAML app, etc.).
18
+ * Absent for OIDC onboards.
13
19
  */
14
20
  export interface OnboardSsoOrgResponse {
15
21
  cognitoProviderName: string;
16
22
  cognitoClientId: string;
17
23
  correlationId: string;
24
+ samlSetup?: SamlIdpSetup;
18
25
  }
@@ -0,0 +1,2 @@
1
+ export * from './request';
2
+ export * from './response';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./request"), exports);
18
+ __exportStar(require("./response"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sso/sso-mappings/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAAyB;AACzB,6CAA0B"}
@@ -0,0 +1,14 @@
1
+ import { RoleMappingInput } from '../../organisations/sso';
2
+ /**
3
+ * Wire shape for `PUT /sso/orgs/:id/sso-mappings`.
4
+ *
5
+ * Atomic full-replace of the org's role-mapping configuration. Sends both
6
+ * `idpRoleClaim` (which IdP claim feeds `custom:g2g_role`) and the whole
7
+ * `roleMappings` array. The orchestrator validates roleIds, pushes any
8
+ * `idpRoleClaim` change to Cognito via `UpdateIdentityProvider`, then
9
+ * persists. Mapping `_id` values are server-assigned on each write.
10
+ */
11
+ export interface UpdateSsoMappingsRequest {
12
+ idpRoleClaim: string;
13
+ roleMappings: RoleMappingInput[];
14
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../../src/sso/sso-mappings/request.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Response shape for `PUT /sso/orgs/:id/sso-mappings`.
3
+ *
4
+ * Just a correlation id for stitching audit-log entries together. The FE
5
+ * re-fetches via `GET /sso/orgs/:id` to render the post-update state.
6
+ */
7
+ export interface UpdateSsoMappingsResponse {
8
+ correlationId: string;
9
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../../src/sso/sso-mappings/response.ts"],"names":[],"mappings":""}
@@ -18,5 +18,12 @@ export interface GetInternalUserResponse extends BaseGetUserResponse {
18
18
  type: 'INTERNAL';
19
19
  email: string;
20
20
  lastLogin?: number;
21
+ /**
22
+ * True while the user has an outstanding invite and has not yet set a
23
+ * password (no Cognito record). Cleared on `POST /users/invites/:token/accept`
24
+ * and absent for users created via the linking branch (existing Cognito
25
+ * record from another org membership).
26
+ */
27
+ invitePending?: boolean;
21
28
  }
22
29
  export declare type GetUserResponse = GetExternalUserResponse | GetInternalUserResponse;
@@ -3,3 +3,4 @@ export * from './current';
3
3
  export * from './post';
4
4
  export * from './get';
5
5
  export * from './[id]';
6
+ export * from './invites';
@@ -19,4 +19,5 @@ __exportStar(require("./current"), exports);
19
19
  __exportStar(require("./post"), exports);
20
20
  __exportStar(require("./get"), exports);
21
21
  __exportStar(require("./[id]"), exports);
22
+ __exportStar(require("./invites"), exports);
22
23
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/users/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA2B;AAC3B,4CAAyB;AACzB,yCAAsB;AACtB,wCAAqB;AACrB,yCAAsB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/users/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA2B;AAC3B,4CAAyB;AACzB,yCAAsB;AACtB,wCAAqB;AACrB,yCAAsB;AACtB,4CAAyB"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Response shape for `GET /users/invites/:token`. Returns minimal context
3
+ * so the FE can render "You've been invited to join <Org>" before the user
4
+ * sets their password. 404 is returned for not-found / expired / consumed
5
+ * (same response for all three to avoid enumeration).
6
+ */
7
+ export interface GetInviteResponse {
8
+ email: string;
9
+ organisationName: string;
10
+ }
11
+ export interface AcceptInviteRequest {
12
+ password: string;
13
+ }
14
+ /**
15
+ * Response shape for `POST /users/invites/:token/accept`. Empty body — a
16
+ * 200 means the Cognito user has been created and the password is set;
17
+ * the FE can redirect to login.
18
+ */
19
+ export declare type AcceptInviteResponse = Record<string, never>;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=invites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.js","sourceRoot":"","sources":["../../src/users/invites.ts"],"names":[],"mappings":""}
@@ -10,6 +10,5 @@ export interface PostExternalUserRequest extends BasePostUserRequest {
10
10
  export interface PostInternalUserRequest extends BasePostUserRequest {
11
11
  type: 'INTERNAL';
12
12
  email: string;
13
- password?: string;
14
13
  }
15
14
  export declare type PostUserRequest = PostExternalUserRequest | PostInternalUserRequest;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spotto/contract",
3
3
  "license": "ISC",
4
- "version": "1.0.69-alpha.24",
4
+ "version": "1.0.69-alpha.26",
5
5
  "description": "Spotto's API Contract type definitions",
6
6
  "main": "./dist/index.js",
7
7
  "files": [
@@ -18,5 +18,5 @@
18
18
  "@types/geojson": "^7946.0.11",
19
19
  "shx": "^0.3.4"
20
20
  },
21
- "gitHead": "892aa327162a98a8547b9e5142afabbe3a814ccd"
21
+ "gitHead": "c9d1c5e6716171c2910b310108a9a55d513fb4f2"
22
22
  }