@bentoforge/umami-iam 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -9,9 +9,7 @@ export interface AccessClaims {
9
9
  aud?: string;
10
10
  /** The active tenant this token is scoped to. */
11
11
  tenant: string;
12
- name: string;
13
12
  email: string;
14
- locale: string;
15
13
  permissions: string[];
16
14
  iat: number;
17
15
  exp: number;
@@ -61,13 +59,30 @@ export interface TotpSetup {
61
59
  secret: string;
62
60
  /** `otpauth://` URL for QR rendering. */
63
61
  otpauthUrl: string;
62
+ /** Ready-to-render QR-code SVG of `otpauthUrl` (dark on white). */
63
+ qrSvg: string;
64
64
  }
65
65
 
66
66
  // ── Users ───────────────────────────────────────────────────────────────────
67
67
 
68
- export type UserStatus = "Active" | "Locked" | "Invited";
68
+ /** How to address a user; the rendered word is composed server-side from the config labels. */
69
+ export type Salutation = "" | "SIR" | "MADAM";
70
+
71
+ /** Structured name parts (editable) plus the server-composed display names (read-only). */
72
+ export interface NameParts {
73
+ title: string | null;
74
+ salutation: Salutation;
75
+ firstname: string | null;
76
+ lastname: string | null;
77
+ /** `title firstname lastname`. */
78
+ name: string;
79
+ /** `salutation title firstname lastname`. */
80
+ fullName: string;
81
+ /** `salutation title lastname`. */
82
+ addressableName: string;
83
+ }
69
84
 
70
- export interface UserView {
85
+ export interface UserView extends NameParts {
71
86
  userId: string;
72
87
  tenantId: string;
73
88
  roles: string[];
@@ -75,95 +90,131 @@ export interface UserView {
75
90
  username: string;
76
91
  /** Optional contact email — not unique, may be null/absent. */
77
92
  email: string | null;
78
- name: string;
79
- locale: string;
80
- status: UserStatus;
93
+ /** Admin lock — a locked user cannot log in. */
94
+ locked: boolean;
81
95
  customFields: Record<string, unknown>;
82
96
  /** RFC3339 creation timestamp. */
83
97
  created: string;
84
- /** RFC3339 timestamp of the user's last authentication (login/refresh). */
85
- lastSeen: string;
86
- }
87
-
88
- export interface CreateUserRequest {
98
+ /** RFC3339 timestamp of the last change to this record. */
99
+ lastUpdated: string;
100
+ /** RFC3339 timestamp of the user's last authentication (login/refresh); null until first active. */
101
+ lastSeen: string | null;
102
+ /** User id that created / last changed this user (audit; not surfaced in the UI yet). */
103
+ createdBy?: string | null;
104
+ lastChangedBy?: string | null;
105
+ /** Whether TOTP MFA is configured (never exposes the secret). */
106
+ mfaEnabled: boolean;
107
+ /** Whether the current password came from an admin reset and hasn't been changed since. */
108
+ passwordGenerated: boolean;
109
+ /** Whether the user has at least one registered passkey. */
110
+ hasPasskey: boolean;
111
+ }
112
+
113
+ /** The editable structured name parts (all optional; omitted = unset, `""` clears). */
114
+ export interface NameInput {
115
+ title?: string;
116
+ salutation?: Salutation;
117
+ firstname?: string;
118
+ lastname?: string;
119
+ }
120
+
121
+ export interface CreateUserRequest extends NameInput {
89
122
  /** Login username (unique). If omitted, `email` is used as the username. */
90
123
  username?: string;
91
124
  /** Optional contact email (not unique). */
92
125
  email?: string;
93
- password: string;
94
- name: string;
95
- locale?: string;
126
+ /** Optional initial password. Omit (the normal case) to have a temporary one generated and
127
+ * returned once in {@link CreateUserResponse.temporaryPassword}. */
128
+ password?: string;
96
129
  roles?: string[];
97
130
  customFields?: Record<string, unknown>;
98
131
  }
99
132
 
100
- export interface PatchUserRequest {
133
+ /** The created user, plus the one-time temporary password when one was generated. */
134
+ export type CreateUserResponse = UserView & {
135
+ /** Present only when the server generated the initial password — shown once. */
136
+ temporaryPassword?: string | null;
137
+ };
138
+
139
+ export interface PatchUserRequest extends NameInput {
140
+ /** New login username (globally unique). Omit to leave unchanged; must not be empty. */
141
+ username?: string;
142
+ /** Contact email. Omit to leave unchanged; empty string clears it. */
143
+ email?: string;
101
144
  roles?: string[];
102
- status?: UserStatus;
145
+ locked?: boolean;
103
146
  customFields?: Record<string, unknown>;
104
147
  }
105
148
 
106
149
  // ── Me ──────────────────────────────────────────────────────────────────────
107
150
 
151
+ export interface MeUser extends NameParts {
152
+ userId: string;
153
+ tenantId: string;
154
+ roles: string[];
155
+ username: string;
156
+ email: string | null;
157
+ locked: boolean;
158
+ customFields: Record<string, unknown>;
159
+ /** Whether TOTP MFA is configured (never exposes the secret). */
160
+ mfaEnabled: boolean;
161
+ /** Whether the caller has at least one registered passkey. */
162
+ hasPasskey: boolean;
163
+ }
164
+
108
165
  export interface MeResponse {
109
- user: {
110
- userId: string;
111
- tenantId: string;
112
- roles: string[];
113
- username: string;
114
- email: string | null;
115
- name: string;
116
- locale: string;
117
- status: UserStatus;
118
- };
166
+ user: MeUser;
119
167
  tenant: Tenant | null;
120
168
  }
121
169
 
122
- // ── Tenants ──────────────────────────────────────────────────────────────────
123
-
124
- export type TenantStatus = "Lead" | "Testing" | "Onboarding" | "Active" | "Suspended" | "Churned";
125
-
126
- export type FeatureToggle = "standard" | "on" | "off";
127
-
128
- export interface PackageAssignment {
129
- id: string;
130
- code: string;
131
- assignedAt: string;
132
- accountedUntil?: string | null;
133
- monthlyPrice?: string | null;
134
- priceFixedUntil?: string | null;
135
- active: boolean;
170
+ /** One of the caller's active login sessions (device). Never exposes the refresh secret. */
171
+ export interface SessionView {
172
+ sessionId: string;
173
+ userAgent?: string;
174
+ ip?: string;
175
+ /** RFC3339 creation timestamp. */
176
+ created: string;
177
+ /** RFC3339 timestamp of the last refresh. */
178
+ lastSeen: string;
179
+ /** RFC3339 absolute expiry. */
180
+ expiresAt: string;
181
+ /** Whether this is the session making the request. */
182
+ current: boolean;
136
183
  }
137
184
 
185
+ // ── Tenants ──────────────────────────────────────────────────────────────────
186
+
138
187
  export interface Tenant {
139
188
  tenantId: string;
140
189
  version: number;
141
- packages: PackageAssignment[];
142
- limitOverrides: Record<string, string>;
143
- featureOverrides: Record<string, FeatureToggle>;
144
190
  /** Authorization features granted to the tenant (`feature:*`), fed to the token broker. */
145
191
  features: string[];
146
192
  customFields: Record<string, unknown>;
147
193
  name: string;
148
194
  slug: string;
149
- status: TenantStatus;
150
- plan: string;
151
- billedUntil?: string | null;
152
- seatsLimit?: number | null;
153
195
  created: string;
154
196
  lastUpdated: string;
197
+ /** RFC3339 timestamp of the last token activity (refresh / exchange) scoped to this tenant;
198
+ * null until the first activity. */
199
+ lastActive?: string | null;
200
+ /** Sort key backing the tenant listing: `lastActive` when present, else `created`. */
201
+ lastActiveOrCreated?: string;
202
+ /** User id that created this tenant (audit; not surfaced in the UI yet). */
203
+ createdBy?: string | null;
204
+ /** User id of the last change to this tenant (audit; not surfaced in the UI yet). */
205
+ lastChangedBy?: string | null;
155
206
  }
156
207
 
157
208
  export interface CreateTenantRequest {
158
209
  name: string;
159
- owner: {
210
+ /** Optional first owner. Omit to create an empty tenant (add users afterwards by impersonating
211
+ * it on the Tenants screen). */
212
+ owner?: {
160
213
  /** Owner login username (unique). If omitted, `email` is used as the username. */
161
214
  username?: string;
162
215
  /** Optional contact email (not unique). */
163
216
  email?: string;
164
217
  password: string;
165
- name: string;
166
- locale?: string;
167
218
  };
168
219
  /** Custom-field values, validated against `customTenantFields`. */
169
220
  customFields?: Record<string, unknown>;
@@ -171,26 +222,8 @@ export interface CreateTenantRequest {
171
222
 
172
223
  export interface CreateTenantResponse {
173
224
  tenantId: string;
174
- ownerUserId: string;
175
- }
176
-
177
- export interface EntitlementsResponse {
178
- limits: Record<string, string>;
179
- features: string[];
180
- monthlyTotal: string;
181
- packages: PackageAssignment[];
182
- }
183
-
184
- export interface MetricUsage {
185
- metric: string;
186
- used: number;
187
- limit?: string;
188
- overQuota: boolean;
189
- }
190
-
191
- export interface UsageResponse {
192
- period: string;
193
- metrics: MetricUsage[];
225
+ /** The created owner's user id — only present when an `owner` was supplied. */
226
+ ownerUserId: string | null;
194
227
  }
195
228
 
196
229
  // ── Config ────────────────────────────────────────────────────────────────────
@@ -199,6 +232,8 @@ export interface UsageResponse {
199
232
  export interface RoleDef {
200
233
  code: string;
201
234
  name: string;
235
+ /** Optional human-readable description (shown muted under the name in the admin UI). */
236
+ description?: string | null;
202
237
  /** Boolean expression over the tenant's `feature:*`/`is:*` gating whether it may be assigned. */
203
238
  assignableIf?: string | null;
204
239
  }
@@ -206,38 +241,21 @@ export interface RoleDef {
206
241
  export interface ScopeDef {
207
242
  code: string;
208
243
  name: string;
244
+ /** Optional human-readable description (shown muted under the name in the admin UI). */
245
+ description?: string | null;
209
246
  assignableIf?: string | null;
210
247
  }
211
248
  /** An authorization feature granted to a tenant (`feature:*`). */
212
249
  export interface FeatureDef {
213
250
  code: string;
214
251
  name: string;
252
+ /** Optional human-readable description (shown muted under the name in the admin UI). */
253
+ description?: string | null;
215
254
  /** Boolean expression over the tenant's current features gating whether it may be granted. */
216
255
  assignableIf?: string | null;
217
256
  }
218
- export interface LimitDef {
219
- code: string;
220
- name: string;
221
- unit?: string;
222
- default?: string;
223
- }
224
- export interface PackageLimit {
225
- code: string;
226
- value: string;
227
- }
228
- export interface PriceEntry {
229
- validFrom: string;
230
- price: string;
231
- }
232
- export interface PackageDef {
233
- code: string;
234
- name: string;
235
- features: string[];
236
- limits: PackageLimit[];
237
- prices: PriceEntry[];
238
- }
239
257
  export interface CustomFieldDef {
240
- key: string;
258
+ code: string;
241
259
  label: string;
242
260
  /** `"string"` | `"number"` | `"bool"` | `"select"`. */
243
261
  type: string;
@@ -246,6 +264,8 @@ export interface CustomFieldDef {
246
264
  required: boolean;
247
265
  /** Whether admin list tables surface this field as a column. */
248
266
  showInTable?: boolean;
267
+ /** Whether the user may edit this field on themselves via `PATCH /auth/me`. */
268
+ selfEditable?: boolean;
249
269
  }
250
270
 
251
271
  /** The custom-field schemas for rendering user/tenant forms (`GET /config/custom-fields`). */
@@ -275,7 +295,9 @@ export interface ApiDef {
275
295
  eligibility?: string | null;
276
296
  /** Ordered rules mapping subjects → granted permissions (accumulated top-to-bottom). */
277
297
  permissions: PermissionRule[];
278
- /** Claim mapping: claimName → source (`customUser:<k>`, `customTenant:<k>`, or a literal). */
298
+ /** Claim mapping: claimName → source. A source is a literal string, a `$user.<field>` /
299
+ * `$tenant.<field>` reference (`id`, `username`, `email`, `name`, `fullName`, `addressableName`,
300
+ * `roles`, `name`/`slug`/`features`, …), or `$user.custom.<key>` / `$tenant.custom.<key>`. */
279
301
  claims?: Record<string, string>;
280
302
  }
281
303
 
@@ -285,10 +307,10 @@ export interface Config {
285
307
  /** Scopes assignable to M2M service keys. */
286
308
  scopes: ScopeDef[];
287
309
  features: FeatureDef[];
288
- limits: LimitDef[];
289
- packages: PackageDef[];
290
310
  customTenantFields: CustomFieldDef[];
291
311
  customUserFields: CustomFieldDef[];
312
+ /** Salutation labels (`salutationCode → word`) in the deployment's language. */
313
+ salutations: Record<string, string>;
292
314
  security: SecuritySettings;
293
315
  /** Messaging integration (Telegram/WhatsApp) settings. */
294
316
  messaging?: MessagingConfig;
@@ -312,8 +334,8 @@ export interface ApiKeyView {
312
334
  roles: string[];
313
335
  /** Service-key `scope:*` subjects (empty for PATs). */
314
336
  scopes: string[];
315
- /** Target API codes this key may mint tokens for. */
316
- apis: string[];
337
+ /** Whether the raw-secret (Mode 1) exchange is allowed; false ⇒ HMAC-only (Mode 2). */
338
+ allowSecretLogin: boolean;
317
339
  status: ApiKeyStatus;
318
340
  allowedOrigins: string[];
319
341
  expiresAt?: string | null;
@@ -326,8 +348,8 @@ export interface CreateApiKeyRequest {
326
348
  name: string;
327
349
  /** The `scope:*` subjects this key carries (must be assignable given the tenant's features). */
328
350
  scopes?: string[];
329
- /** Target API codes this key may mint for; defaults to `["umami"]`. */
330
- apis?: string[];
351
+ /** Allow the raw-secret (Mode 1) exchange; omitted/false HMAC-only (Mode 2). */
352
+ allowSecretLogin?: boolean;
331
353
  allowedOrigins?: string[];
332
354
  expiresAt?: string;
333
355
  }
@@ -337,8 +359,6 @@ export interface CreatePatRequest {
337
359
  name: string;
338
360
  /** Restrict the token to this subset of your own `role:*` (empty = all your roles). */
339
361
  roles?: string[];
340
- /** Target API codes this PAT may mint for; defaults to `["umami"]`. */
341
- apis?: string[];
342
362
  expiresAt?: string;
343
363
  }
344
364
 
@@ -360,6 +380,9 @@ export interface BrandingConfig {
360
380
  /** Logo for dark backgrounds; falls back to logoLight, then default. */
361
381
  logoDark?: string;
362
382
  favicon?: string;
383
+ /** Browser tab title (document `<title>`); served at /app/branding.json, applied at runtime.
384
+ * Empty → "umami". */
385
+ title?: string;
363
386
  }
364
387
 
365
388
  /** Messaging integration settings (Telegram/WhatsApp). */
@@ -394,9 +417,7 @@ export interface MessagingLink {
394
417
  export interface ResolvedMessagingUser {
395
418
  userId: string;
396
419
  tenantId: string;
397
- name: string;
398
420
  email?: string | null;
399
- locale: string;
400
421
  roles: string[];
401
422
  }
402
423
 
@@ -405,6 +426,12 @@ export interface ResolvedMessagingUser {
405
426
  /** Outcome flavour of an audited event. */
406
427
  export type AuditSeverity = "good" | "neutral" | "bad";
407
428
 
429
+ /** One page of audit entries plus the cursor to fetch the next (absent when the trail is exhausted). */
430
+ export interface AuditPage {
431
+ entries: AuditEntry[];
432
+ nextCursor?: string;
433
+ }
434
+
408
435
  export interface AuditEntry {
409
436
  id: string;
410
437
  /** RFC3339 event time. */
@@ -413,6 +440,9 @@ export interface AuditEntry {
413
440
  user?: string | null;
414
441
  severity: AuditSeverity;
415
442
  message: string;
443
+ /** Best-effort client IP, present on security-relevant events (logins, credential/account
444
+ * changes). Absent on events with no request IP. */
445
+ ip?: string | null;
416
446
  }
417
447
 
418
448
  /** Result of an admin password reset — `temporaryPassword` is set (once) only when generated. */