@proveanything/smartlinks 1.15.17 → 1.15.19

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.
@@ -0,0 +1,232 @@
1
+ # Proof Share Grants
2
+
3
+ Delegated, scoped, **revocable** bearer access to a single proof — the middle tier
4
+ between "public" (everyone) and "owner" (only the signed-in owner).
5
+
6
+ A grant lets an owner hand out a link that lets specific recipients **see or do
7
+ specific things on one proof for a limited time**, without those recipients needing
8
+ a SmartLinks account or a proof claim. Typical uses: sharing a private photo album,
9
+ letting guests comment on it, or publishing a verifiable "I own this" assertion.
10
+
11
+ ---
12
+
13
+ ## Concepts
14
+
15
+ A **grant** is a row issued by the proof owner (or a collection admin) and redeemed
16
+ by a bearer holding an opaque token. Every data request that touches the proof
17
+ re-checks the grant **server-side** against the database, so revocation is immediate.
18
+
19
+ ### Scopes — what a grant authorises
20
+
21
+ | Scope | Grants the bearer… |
22
+ |-------|--------------------|
23
+ | `read` | read owner-tier data on the proof (attestations, threads, records, cases) |
24
+ | `comment` | create threads/replies on the proof (guest comments) |
25
+ | `admin` | read owner-tier data (reserved for elevated share cases; never exposes the platform admin zone) |
26
+ | `verify_owner` | redeem a shareable ownership **assertion** (not the account) |
27
+
28
+ A grant can carry several scopes, e.g. `['read', 'comment']` for a shareable,
29
+ commentable album.
30
+
31
+ ### Security & lifecycle
32
+
33
+ - The token is opaque, unguessable, and returned to the issuer **exactly once** (on `createGrant`). It is never returned by `listGrants`.
34
+ - **Revocation is immediate** — the grant is re-checked on every request, so `revokeGrant` invalidates a token across all clients at once.
35
+ - **Auto-invalidation on transfer** — every grant is voided the moment the proof's `ownerId` changes (e.g. a resale/re-claim), so a stale "I own this" link cannot keep resolving.
36
+ - A grant is scoped to **one proof**; it can never widen access to other proofs or collection-level data.
37
+
38
+ ---
39
+
40
+ ## Owner flow — create, list, revoke
41
+
42
+ ```typescript
43
+ import { proof } from '@proveanything/smartlinks'
44
+
45
+ // Create a read+comment grant that expires in 7 days.
46
+ const grant = await proof.createGrant(collectionId, productId, proofId, {
47
+ scope: ['read', 'comment'],
48
+ audience: { kind: 'public_link' },
49
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
50
+ })
51
+
52
+ // `grant.token` is available ONLY here — embed it in your share link now.
53
+ const shareUrl = `https://app.example.com/album?proofId=${proofId}&shareToken=${grant.token}`
54
+
55
+ // List active + past grants (tokens are never included).
56
+ const grants = await proof.listGrants(collectionId, productId, proofId)
57
+
58
+ // Stop sharing — takes effect on the very next request from any client.
59
+ await proof.revokeGrant(collectionId, productId, proofId, grant.grantId)
60
+ ```
61
+
62
+ `createGrant` / `listGrants` / `revokeGrant` require the caller to be the **proof
63
+ owner** (or a collection admin) — i.e. a signed-in user whose `bearerToken` is set.
64
+
65
+ ---
66
+
67
+ ## Recipient flow — redeem, then carry the token
68
+
69
+ A recipient opens the share link, redeems the token once, then sets it as the
70
+ active grant token. From then on **every** SDK request carries the token
71
+ (`X-Grant-Token`), so all proof reads/writes are evaluated against the grant.
72
+
73
+ ```typescript
74
+ import { proof, setGrantToken } from '@proveanything/smartlinks'
75
+
76
+ const shareToken = new URLSearchParams(location.search).get('shareToken')!
77
+
78
+ // Redeem once (anonymous or signed-in). Records the redemption; optionally names the guest.
79
+ await proof.redeemGrant(collectionId, productId, proofId, shareToken, {
80
+ guestName: 'Sam', // stamped on guest activity when not signed in
81
+ })
82
+
83
+ // Attach the token to every subsequent request.
84
+ setGrantToken(shareToken)
85
+
86
+ // Now grant-tier reads succeed — e.g. owner-visibility memories on the proof:
87
+ const { attestations } = await attestation.publicList(collectionId, {
88
+ subjectType: 'proof', subjectId: proofId,
89
+ })
90
+
91
+ // Clear it when leaving the shared view:
92
+ setGrantToken(undefined)
93
+ ```
94
+
95
+ > **Persisting across reloads.** `setGrantToken` holds the token in memory. To keep a
96
+ > shared session across reloads, persist `shareToken` yourself (e.g. in `localStorage`)
97
+ > and call `setGrantToken` again on load.
98
+
99
+ ---
100
+
101
+ ## Guest commenting
102
+
103
+ With a `comment` scope grant, a bearer can post comments even when they are not
104
+ signed in. The app must enable the **grant branch** of the thread create policy
105
+ (see [App config](#app-config)); comments are then created at `visibility: 'owner'`
106
+ (private to the proof) and stamped `authorType: 'guest'`.
107
+
108
+ ```typescript
109
+ import { app } from '@proveanything/smartlinks'
110
+ // setGrantToken(shareToken) has already been called.
111
+
112
+ // One atomic call — no separate create-then-reply round trip.
113
+ await app.threads.create(collectionId, 'photo-memory', {
114
+ parentType: 'memory',
115
+ parentId: memoryId, // text — SmartLinks short ids are fine (not just UUIDs)
116
+ proofId, // anchor to the proof so grant readers see it
117
+ firstReply: { text: 'Lovely photo', authorName: 'Sam' },
118
+ })
119
+ ```
120
+
121
+ Other grant holders (and the owner) see these comments because a `read` grant reveals
122
+ `owner`-visibility threads for the proof. See
123
+ [App Objects → Threads](app-objects.md#threads) for the full threads API.
124
+
125
+ ---
126
+
127
+ ## Proof of ownership — `verify_owner`
128
+
129
+ Ownership itself is **not** a grant — it is `proof.ownerId`, established via the
130
+ existing [claim flow](proof-claiming-methods.md). A `verify_owner` grant only
131
+ publishes a shareable, verifiable **assertion** derived from that ownership, without
132
+ handing over the account:
133
+
134
+ ```typescript
135
+ const grant = await proof.createGrant(collectionId, productId, proofId, {
136
+ scope: ['verify_owner'],
137
+ })
138
+
139
+ // The recipient redeems it and gets the assertion — never the account:
140
+ const result = await proof.redeemGrant(collectionId, productId, proofId, grant.token)
141
+ // { proofId, assertsOwnership: true, ownerDisplayName?, issuedAt, expiresAt }
142
+ ```
143
+
144
+ Because grants auto-invalidate on transfer, a resale cannot leave a stale
145
+ "I own this" link in circulation.
146
+
147
+ ---
148
+
149
+ ## What a grant gates
150
+
151
+ When a valid grant token is present, these public reads elevate to owner-tier for the
152
+ granted proof (and only that proof):
153
+
154
+ - **Attestations** — `attestation.publicList({ subjectType: 'proof', subjectId })`
155
+ - **Threads / Records / Cases** — `app.threads.list`, `app.records.*`, `app.cases.list`, and the single-item GETs, filtered to the granted proof
156
+ - **Thread creation / replies** — with a `comment` scope grant (see below)
157
+
158
+ The token never exposes the platform `admin` zone, and only reveals `owner`-visibility
159
+ rows for the granted `proofId`.
160
+
161
+ ---
162
+
163
+ ## App config
164
+
165
+ Grant-based commenting is opt-in per app, configured on the app's Firestore config
166
+ at `sites/{collectionId}/apps/{appId}` — a `grant` branch alongside
167
+ `anonymous` / `authenticated`:
168
+
169
+ ```jsonc
170
+ {
171
+ "publicCreate": {
172
+ "threads": {
173
+ "grant": {
174
+ "allow": true,
175
+ "requireScope": "comment",
176
+ "enforce": { "visibility": "owner", "status": "open" }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ This enables grant-scoped commenting **without** opening up anonymous creation. The
184
+ `enforce.visibility: "owner"` keeps comments private to the proof (visible to the
185
+ owner and other grant holders, not the wider public).
186
+
187
+ ---
188
+
189
+ ## API reference
190
+
191
+ ```typescript
192
+ namespace proof {
193
+ createGrant(collectionId, productId, proofId, options: CreateGrantOptions): Promise<ProofGrant>
194
+ listGrants(collectionId, productId, proofId): Promise<ProofGrant[]>
195
+ revokeGrant(collectionId, productId, proofId, grantId): Promise<void>
196
+ redeemGrant(collectionId, productId, proofId, token, options?: RedeemGrantOptions): Promise<RedeemGrantResult>
197
+ }
198
+
199
+ // Attach / clear the active grant token (sent as X-Grant-Token on every request).
200
+ function setGrantToken(token: string | undefined): void
201
+ function getGrantToken(): string | undefined
202
+
203
+ type GrantScope = 'read' | 'comment' | 'admin' | 'verify_owner'
204
+
205
+ interface CreateGrantOptions {
206
+ scope: GrantScope[] // at least one
207
+ audience?: { kind: 'public_link' } | { kind: 'named'; email?: string; userId?: string }
208
+ expiresAt?: Date | string
209
+ }
210
+
211
+ interface RedeemGrantOptions { guestName?: string }
212
+
213
+ type RedeemGrantResult =
214
+ | { scope: GrantScope[]; redeemedAt: string }
215
+ | { proofId: string; assertsOwnership: true; ownerDisplayName?: string; issuedAt?: string; expiresAt?: string }
216
+
217
+ interface ProofGrant {
218
+ grantId: string
219
+ proofId: string
220
+ productId?: string | null
221
+ scope: GrantScope[]
222
+ audience: { kind: 'public_link' | 'named'; email?: string; userId?: string }
223
+ createdBy: string
224
+ expiresAt?: string | null
225
+ revokedAt?: string | null
226
+ redeemedBy?: { userId?: string; guestName?: string; redeemedAt: string }
227
+ redeemCount: number
228
+ createdAt: string
229
+ updatedAt: string
230
+ token?: string // present ONLY on the createGrant response
231
+ }
232
+ ```
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export type { AdditionalGtin, ISODateString, JsonPrimitive, JsonValue, ProductCr
17
17
  export type { TranslationLookupMode, TranslationContentType, TranslationQuality, TranslationItemStatus, TranslationContextValue, TranslationContext, TranslationLookupRequestBase, TranslationLookupSingleRequest, TranslationLookupBatchRequest, TranslationLookupRequest, TranslationLookupItem, TranslationLookupResponse, ResolvedTranslationItem, ResolvedTranslationResponse, TranslationHashOptions, TranslationResolveOptions, TranslationRecord, TranslationListParams, TranslationListResponse, TranslationUpdateRequest, } from "./types/translations";
18
18
  export type { FacetBucket, FacetDefinition, FacetDefinitionWriteInput, FacetGetParams, FacetListParams, FacetListResponse, FacetNamespaceListResponse, FacetQueryRequest, FacetQueryResponse, FacetValue, FacetValueDefinition, FacetValueGetParams, FacetValueListParams, FacetValueListResponse, FacetValueResponse, FacetValueWriteInput, PublicFacetListParams, } from "./types/facets";
19
19
  export type { Collection, CollectionResponse, CollectionCreateRequest, CollectionUpdateRequest, DomainTarget, HubAvailabilityResponse, } from "./types/collection";
20
- export type { Proof, ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, ProofGrant, GrantScope, GrantAudience, CreateGrantOptions, RedeemGrantOptions, RedeemGrantResult, } from "./types/proof";
20
+ export type { Proof, ProofResponse, ProofWrite, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, ProofGrant, GrantScope, GrantAudience, CreateGrantOptions, RedeemGrantOptions, RedeemGrantResult, } from "./types/proof";
21
21
  export type { QrShortCodeLookupResponse, } from "./types/qr";
22
22
  export type { ReverseTagLookupParams, ReverseTagLookupResponse, } from "./types/tags";
23
23
  export type { AdminMobileCapability, ActionableCapability, AdminMobileHostId, AdminMobileEvent, AdminMobileEventCallback, AdminMobileEventSubscriber, ScannerEventSubscriber, // @deprecated — use AdminMobileEventCallback
@@ -25,4 +25,4 @@ AdminMobileHostContext, AdminMobileComponentManifest, AdminMobileBundleManifest,
25
25
  MobileAdminBundleManifest, } from './mobile-admin/types';
26
26
  export { HostCapabilityUnavailableError, HostPermissionDeniedError, HostTimeoutError, } from './mobile-admin/errors';
27
27
  export type { NativeCapability, NativeFacade, ShareFacade, ClipboardFacade, HapticImpactStyle, HapticNotificationStyle, HapticsFacade, NetworkStatus, NetworkFacade, DeviceInfo, DeviceFacade, StorageFacade, QrScanOptions, QrFacade, AuthFacade, NfcReadResult, NfcFacade, RfidScanOptions, RfidFacade, EventsFacade, WebSourceMode, WebSourceConfig, WebSourceFacade, } from './native/types';
28
- export type { AuthKitUser, UserProfile, ProfileUpdateData, UpdateProfileResponse, SuccessResponse, AuthLoginResponse, AppleLoginOptions, AuthKitErrorCode, RefreshResponse, LogoutResponse, RefreshErrorCode, MagicLinkSendResponse, MagicLinkVerifyResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, VerifyStatus, WhatsAppReplyCta, WhatsAppReplyOptions, WhatsAppContactData, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse, AuthKitBrandingConfig, AuthKitConfig, } from './types/authKit';
28
+ export type { AuthKitUser, UserProfile, ProfileUpdateData, UpdateProfileResponse, SuccessResponse, AuthLoginResponse, AppleLoginOptions, AuthKitErrorCode, RefreshResponse, LogoutResponse, RefreshErrorCode, MagicLinkSendResponse, MagicLinkVerifyResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, VerifyStatus, WhatsAppReplyCta, WhatsAppReplyOptions, WhatsAppContactData, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse, AuthKitBrandingConfig, AuthKitConfig, AuthKitSecurityConfig, AuthKitPasswordPolicy, AuthKitSessionPolicy, AuthKitLockoutPolicy, PasswordPolicyErrorCode, LoginSecurityErrorCode, } from './types/authKit';
package/dist/openapi.yaml CHANGED
@@ -8401,7 +8401,7 @@ paths:
8401
8401
  post:
8402
8402
  tags:
8403
8403
  - authKit
8404
- summary: Login with email + password (public).
8404
+ summary: authKit.login
8405
8405
  operationId: authKit_login
8406
8406
  security: []
8407
8407
  parameters:
@@ -13941,6 +13941,23 @@ paths:
13941
13941
  required: false
13942
13942
  schema:
13943
13943
  type: string
13944
+ - name: parentIds
13945
+ in: query
13946
+ required: false
13947
+ schema:
13948
+ type: array
13949
+ items:
13950
+ type: string
13951
+ - name: proofId
13952
+ in: query
13953
+ required: false
13954
+ schema:
13955
+ type: string
13956
+ - name: productId
13957
+ in: query
13958
+ required: false
13959
+ schema:
13960
+ type: string
13944
13961
  - name: tag
13945
13962
  in: query
13946
13963
  required: false
@@ -14182,6 +14199,52 @@ paths:
14182
14199
  application/json:
14183
14200
  schema:
14184
14201
  $ref: "#/components/schemas/ReplyInput"
14202
+ /{zone}/collection/{collectionId}/app/{appId}/threads/{threadId}/reply/{replyId}:
14203
+ delete:
14204
+ tags:
14205
+ - threads
14206
+ summary: Delete a single reply from a thread by its reply id (moderation).
14207
+ operationId: threads_deleteReply
14208
+ security: []
14209
+ parameters:
14210
+ - name: zone
14211
+ in: path
14212
+ required: true
14213
+ schema:
14214
+ type: string
14215
+ - name: collectionId
14216
+ in: path
14217
+ required: true
14218
+ schema:
14219
+ type: string
14220
+ - name: appId
14221
+ in: path
14222
+ required: true
14223
+ schema:
14224
+ type: string
14225
+ - name: threadId
14226
+ in: path
14227
+ required: true
14228
+ schema:
14229
+ type: string
14230
+ - name: replyId
14231
+ in: path
14232
+ required: true
14233
+ schema:
14234
+ type: string
14235
+ responses:
14236
+ 200:
14237
+ description: Success
14238
+ content:
14239
+ application/json:
14240
+ schema:
14241
+ $ref: "#/components/schemas/AppThread"
14242
+ 400:
14243
+ description: Bad request
14244
+ 401:
14245
+ description: Unauthorized
14246
+ 404:
14247
+ description: Not found
14185
14248
  components:
14186
14249
  securitySchemes:
14187
14250
  bearerAuth:
@@ -16967,6 +17030,8 @@ components:
16967
17030
  admin:
16968
17031
  type: object
16969
17032
  additionalProperties: true
17033
+ firstReply:
17034
+ $ref: "#/components/schemas/ReplyInput"
16970
17035
  UpdateThreadInput:
16971
17036
  type: object
16972
17037
  properties:
@@ -17012,6 +17077,14 @@ components:
17012
17077
  type: string
17013
17078
  parentId:
17014
17079
  type: string
17080
+ parentIds:
17081
+ type: array
17082
+ items:
17083
+ type: string
17084
+ proofId:
17085
+ type: string
17086
+ productId:
17087
+ type: string
17015
17088
  tag:
17016
17089
  type: string
17017
17090
  contactId:
@@ -19308,8 +19381,69 @@ components:
19308
19381
  type: string
19309
19382
  updatedAt:
19310
19383
  type: string
19384
+ security:
19385
+ $ref: "#/components/schemas/AuthKitSecurityConfig"
19311
19386
  required:
19312
19387
  - id
19388
+ AuthKitSecurityConfig:
19389
+ type: object
19390
+ properties:
19391
+ passwordPolicy:
19392
+ $ref: "#/components/schemas/AuthKitPasswordPolicy"
19393
+ session:
19394
+ $ref: "#/components/schemas/AuthKitSessionPolicy"
19395
+ lockout:
19396
+ $ref: "#/components/schemas/AuthKitLockoutPolicy"
19397
+ AuthKitPasswordPolicy:
19398
+ type: object
19399
+ properties:
19400
+ minLength:
19401
+ type: number
19402
+ requireUppercase:
19403
+ type: boolean
19404
+ requireLowercase:
19405
+ type: boolean
19406
+ requireNumber:
19407
+ type: boolean
19408
+ requireSymbol:
19409
+ type: boolean
19410
+ blockCommonPasswords:
19411
+ type: boolean
19412
+ expiryDays:
19413
+ type: number
19414
+ historyCount:
19415
+ type: number
19416
+ AuthKitSessionPolicy:
19417
+ type: object
19418
+ properties:
19419
+ inactivityTimeoutMinutes:
19420
+ type: number
19421
+ inactivityWarningSeconds:
19422
+ type: number
19423
+ absoluteTimeoutHours:
19424
+ type: number
19425
+ rememberMe:
19426
+ type: boolean
19427
+ AuthKitLockoutPolicy:
19428
+ type: object
19429
+ properties:
19430
+ enabled:
19431
+ type: boolean
19432
+ maxFailedAttempts:
19433
+ type: number
19434
+ attemptWindowMinutes:
19435
+ type: number
19436
+ lockoutMinutes:
19437
+ type: number
19438
+ notifyUserOnLockout:
19439
+ type: boolean
19440
+ PasswordPolicyErrorCode:
19441
+ type: string
19442
+ enum:
19443
+ - PASSWORD_TOO_SHORT
19444
+ - PASSWORD_REQUIREMENTS_NOT_MET
19445
+ - PASSWORD_TOO_COMMON
19446
+ - PASSWORD_RECENTLY_USED
19313
19447
  FirebaseTimestamp:
19314
19448
  type: object
19315
19449
  properties:
@@ -24921,9 +25055,11 @@ components:
24921
25055
  - tokenId
24922
25056
  - userId
24923
25057
  - values
24924
- ProofCreateRequest:
25058
+ ProofWrite:
24925
25059
  type: object
24926
25060
  properties:
25061
+ id:
25062
+ type: string
24927
25063
  values:
24928
25064
  $ref: "#/components/schemas/ProofValues"
24929
25065
  data:
@@ -24934,12 +25070,33 @@ components:
24934
25070
  type: object
24935
25071
  additionalProperties:
24936
25072
  $ref: "#/components/schemas/JsonValue"
25073
+ owner:
25074
+ type: object
25075
+ additionalProperties:
25076
+ $ref: "#/components/schemas/JsonValue"
25077
+ claimable:
25078
+ type: boolean
25079
+ ProofCreateRequest:
25080
+ type: object
25081
+ properties:
25082
+ proof:
25083
+ $ref: "#/components/schemas/ProofWrite"
25084
+ values:
25085
+ $ref: "#/components/schemas/ProofValues"
24937
25086
  claimable:
24938
25087
  type: boolean
24939
25088
  virtual:
24940
25089
  type: boolean
24941
- required:
24942
- - values
25090
+ core:
25091
+ $ref: "#/components/schemas/ProofWrite"
25092
+ data:
25093
+ type: object
25094
+ additionalProperties:
25095
+ $ref: "#/components/schemas/JsonValue"
25096
+ admin:
25097
+ type: object
25098
+ additionalProperties:
25099
+ $ref: "#/components/schemas/JsonValue"
24943
25100
  ProofFieldsConfig:
24944
25101
  type: object
24945
25102
  properties:
@@ -25023,9 +25180,6 @@ components:
25023
25180
  properties:
25024
25181
  guestName:
25025
25182
  type: string
25026
- ProofResponse:
25027
- type: object
25028
- additionalProperties: true
25029
25183
  QrShortCodeLookupResponse:
25030
25184
  type: object
25031
25185
  properties:
@@ -250,6 +250,12 @@ export interface CreateThreadInput {
250
250
  data?: Record<string, unknown>;
251
251
  owner?: Record<string, unknown>;
252
252
  admin?: Record<string, unknown>;
253
+ /**
254
+ * Optional atomic first reply. Posting a comment no longer needs a separate
255
+ * create-thread-then-reply round trip (which could orphan an empty thread on
256
+ * partial failure). The reply is stored with a generated `id` and timestamp.
257
+ */
258
+ firstReply?: ReplyInput;
253
259
  }
254
260
  /**
255
261
  * Input for updating a thread
@@ -279,8 +285,16 @@ export interface ReplyInput {
279
285
  export interface ThreadListQueryParams extends ListQueryParams {
280
286
  slug?: string;
281
287
  authorId?: string;
288
+ /** Disambiguates the parent entity kind (e.g. "memory", "case", "proof"). */
282
289
  parentType?: string;
290
+ /** Anchor to a single app entity id (text — SmartLinks short ids, not UUIDs). */
283
291
  parentId?: string;
292
+ /** Batch-fetch threads for many app entities in one call (e.g. a memory feed). */
293
+ parentIds?: string[];
294
+ /** Anchor threads to a proof. For grant-token callers this is the enforced filter. */
295
+ proofId?: string;
296
+ /** Anchor threads to a product (one tier up from proof). */
297
+ productId?: string;
284
298
  tag?: string;
285
299
  contactId?: string;
286
300
  }
@@ -89,8 +89,11 @@ export interface LogoutResponse {
89
89
  * - `INVALID_REFRESH_TOKEN` (401) — unknown / expired / revoked / wrong client → `logout()` + route to login.
90
90
  * - `REFRESH_TOKEN_REUSE_DETECTED` (401) — a consumed token was replayed; the **entire session
91
91
  * family was revoked server-side**. Hard logout: clear storage, force re-login.
92
+ * - `SESSION_EXPIRED` (401) — the session hit the collection's absolute session timeout
93
+ * (`security.session.absoluteTimeoutHours`). Clear storage and route to login. Distinct from
94
+ * `INVALID_REFRESH_TOKEN` so the UI can message "your session expired" rather than an error.
92
95
  */
93
- export type RefreshErrorCode = 'MISSING_REFRESH_TOKEN' | 'INVALID_REFRESH_TOKEN' | 'REFRESH_TOKEN_REUSE_DETECTED';
96
+ export type RefreshErrorCode = 'MISSING_REFRESH_TOKEN' | 'INVALID_REFRESH_TOKEN' | 'REFRESH_TOKEN_REUSE_DETECTED' | 'SESSION_EXPIRED';
94
97
  /**
95
98
  * Options for {@link authKit.appleLogin}. All fields are optional — only the
96
99
  * `identityToken` (passed as a positional argument) is required by the server.
@@ -397,4 +400,67 @@ export interface AuthKitConfig {
397
400
  supportEmail?: string;
398
401
  redirectUrl?: string;
399
402
  updatedAt?: string;
403
+ /**
404
+ * Per-collection security policy. On the public config endpoint only
405
+ * `passwordPolicy` + `session` are returned (the client renders password
406
+ * checklists / idle sign-out from them); `lockout` is admin-only and enforced
407
+ * server-side. See {@link AuthKitSecurityConfig}.
408
+ */
409
+ security?: AuthKitSecurityConfig;
410
+ }
411
+ /**
412
+ * Per-collection account-security policy. The API **enforces** all of this; the
413
+ * client uses `passwordPolicy` (live checklist) and `session` (idle sign-out) for UX.
414
+ */
415
+ export interface AuthKitSecurityConfig {
416
+ passwordPolicy?: AuthKitPasswordPolicy;
417
+ session?: AuthKitSessionPolicy;
418
+ /** Admin-only; never returned on the public config endpoint. */
419
+ lockout?: AuthKitLockoutPolicy;
420
+ }
421
+ export interface AuthKitPasswordPolicy {
422
+ minLength?: number;
423
+ requireUppercase?: boolean;
424
+ requireLowercase?: boolean;
425
+ requireNumber?: boolean;
426
+ requireSymbol?: boolean;
427
+ blockCommonPasswords?: boolean;
428
+ /** 0 = never expires. */
429
+ expiryDays?: number;
430
+ /** 0 = reuse allowed. */
431
+ historyCount?: number;
432
+ }
433
+ export interface AuthKitSessionPolicy {
434
+ /** 0 = never (client-enforced idle sign-out). */
435
+ inactivityTimeoutMinutes?: number;
436
+ inactivityWarningSeconds?: number;
437
+ /** 0 = use token lifetime. Enforced server-side on the native refresh path (→ `SESSION_EXPIRED`). */
438
+ absoluteTimeoutHours?: number;
439
+ rememberMe?: boolean;
440
+ }
441
+ /** Admin-only lockout policy (operational; not exposed publicly). */
442
+ export interface AuthKitLockoutPolicy {
443
+ enabled?: boolean;
444
+ maxFailedAttempts?: number;
445
+ attemptWindowMinutes?: number;
446
+ lockoutMinutes?: number;
447
+ notifyUserOnLockout?: boolean;
400
448
  }
449
+ /**
450
+ * Password-policy validation errors (400) returned by `register`, `completePasswordReset`,
451
+ * and `changePassword`. Surfaced via `SmartlinksApiError.errorCode`.
452
+ * - `PASSWORD_TOO_SHORT` — below `passwordPolicy.minLength`.
453
+ * - `PASSWORD_REQUIREMENTS_NOT_MET` — missing a required character class.
454
+ * - `PASSWORD_TOO_COMMON` — matched the common/breached list.
455
+ * - `PASSWORD_RECENTLY_USED` — matched one of the last `historyCount` passwords.
456
+ */
457
+ export type PasswordPolicyErrorCode = 'PASSWORD_TOO_SHORT' | 'PASSWORD_REQUIREMENTS_NOT_MET' | 'PASSWORD_TOO_COMMON' | 'PASSWORD_RECENTLY_USED';
458
+ /**
459
+ * Security errors returned by `login`. Surfaced via `SmartlinksApiError.errorCode`,
460
+ * with extra fields in `SmartlinksApiError.details`:
461
+ * - `ACCOUNT_TEMPORARILY_LOCKED` (429) — too many failed attempts; `details.retryAfterSeconds`
462
+ * says how long to wait. Show a "try again in N minutes" message.
463
+ * - `PASSWORD_EXPIRED` (403) — password older than `passwordPolicy.expiryDays`; `details.resetToken`
464
+ * is a short-lived token — send the user straight into `completePasswordReset()` to change it in place.
465
+ */
466
+ export type LoginSecurityErrorCode = 'ACCOUNT_TEMPORARILY_LOCKED' | 'PASSWORD_EXPIRED';
@@ -40,16 +40,71 @@ export interface Proof {
40
40
  values: ProofValues;
41
41
  }
42
42
  export type ProofResponse = Proof;
43
- export interface ProofCreateRequest {
44
- values: ProofValues;
45
- /** Business-writable public spec data. */
43
+ /**
44
+ * The proof's writable content, addressed by zone. Its keys mirror the proof
45
+ * document, so what you pass is what the proof looks like. Each zone has its own
46
+ * read/write visibility:
47
+ *
48
+ * | Zone | Stored at | Readable by | Writable by |
49
+ * |----------|----------------|------------------------|---------------|
50
+ * | `values` | `proof.values` | public + owner + admin | owner + admin |
51
+ * | `data` | `proof.data` | public + owner + admin | admin only |
52
+ * | `admin` | `proof.admin` | admin only | admin only |
53
+ * | `owner` | `proof.owner` | admin only | admin only |
54
+ *
55
+ * Use `data` for business fields everyone should *see* but only the business
56
+ * should *set* (e.g. a serial number). Use `admin` for fields only the business
57
+ * should see at all. On update, object zones deep-merge.
58
+ */
59
+ export interface ProofWrite {
60
+ /**
61
+ * Choose the proof's ID (serial, NFC id, etc.). Honoured **on create only** —
62
+ * the ledger doc becomes `{productId}-{id}`. Omit to auto-generate. Ignored on
63
+ * update (a proof's ID is immutable).
64
+ */
65
+ id?: string;
66
+ /** Owner + business-writable consumer data. Public + owner readable. → `proof.values` */
67
+ values?: ProofValues;
68
+ /** Business spec data (e.g. serialNo). Public + owner readable, admin-only writable. → `proof.data` */
46
69
  data?: Record<string, JsonValue>;
47
- /** Business-only spec data. */
70
+ /** Business-only spec data — admin-only read & write (stripped from public + owner). → `proof.admin` */
48
71
  admin?: Record<string, JsonValue>;
72
+ /** Business-only root data — admin-only. → `proof.owner` */
73
+ owner?: Record<string, JsonValue>;
74
+ /** Is this proof available to be claimed. */
75
+ claimable?: boolean;
76
+ /** Any other named root field. */
77
+ [key: string]: JsonValue | Record<string, JsonValue> | ProofValues | undefined;
78
+ }
79
+ export interface ProofCreateRequest {
80
+ /**
81
+ * The proof to create, by zone (mirrors the proof document). This is the clear,
82
+ * recommended shape — `create(collectionId, productId, { proof: {...} })`.
83
+ */
84
+ proof?: ProofWrite;
85
+ /** Owner + business-writable consumer data (→ `proof.values`). Same as `proof.values`. */
86
+ values?: ProofValues;
87
+ /** Canonical root `claimable` flag (also accepted as `proof.claimable`). */
49
88
  claimable?: boolean;
50
89
  virtual?: boolean;
90
+ /** @deprecated Legacy alias for `proof`. Use `proof`. */
91
+ core?: ProofWrite;
92
+ /**
93
+ * @deprecated On the request body this is folded into the **values bag**
94
+ * (public + owner-writable) — NOT `proof.data`. Use `proof.data`.
95
+ */
96
+ data?: Record<string, JsonValue>;
97
+ /** @deprecated Not routed to `proof.admin` on create — use `proof.admin`. */
98
+ admin?: Record<string, JsonValue>;
51
99
  }
52
- export type ProofUpdateRequest = Partial<ProofCreateRequest>;
100
+ /**
101
+ * Update passes the proof's fields **at the root** — `update(c, p, id, { data: {...} })`
102
+ * sets `proof.data`, `{ values: {...} }` sets `proof.values`, etc. (A `proof` block is
103
+ * also accepted and routed the same way.) Object zones deep-merge.
104
+ */
105
+ export type ProofUpdateRequest = Partial<ProofWrite> & {
106
+ proof?: ProofWrite;
107
+ };
53
108
  export type ProofClaimRequest = Record<string, any>;
54
109
  /**
55
110
  * `'public'` (default, omitted) reads/writes `proof.values[key]`.