@proveanything/smartlinks 1.15.13 → 1.15.15
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/dist/api/authKit.d.ts +52 -10
- package/dist/api/authKit.js +56 -14
- package/dist/docs/API_SUMMARY.md +34 -12
- package/dist/docs/auth-kit.md +33 -11
- package/dist/http.d.ts +6 -0
- package/dist/http.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/openapi.yaml +23 -1
- package/dist/types/authKit.d.ts +15 -6
- package/dist/types/collection.d.ts +23 -0
- package/docs/API_SUMMARY.md +34 -12
- package/docs/auth-kit.md +33 -11
- package/openapi.yaml +23 -1
- package/package.json +1 -1
package/dist/api/authKit.d.ts
CHANGED
|
@@ -20,15 +20,28 @@ export declare namespace authKit {
|
|
|
20
20
|
* fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
21
21
|
*/
|
|
22
22
|
function login(clientId: string, email: string, password: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Register a new user (public).
|
|
25
|
+
*
|
|
26
|
+
* Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
|
|
27
|
+
* nothing to challenge against.
|
|
28
|
+
*/
|
|
24
29
|
function register(clientId: string, data: {
|
|
25
30
|
email: string;
|
|
26
31
|
password: string;
|
|
27
32
|
displayName?: string;
|
|
28
33
|
accountData?: Record<string, any>;
|
|
29
34
|
}): Promise<AuthLoginResponse>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Google OAuth login via ID token (public).
|
|
37
|
+
*
|
|
38
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
39
|
+
*
|
|
40
|
+
* @param trustedDeviceToken - Optional. Pass a token previously returned by
|
|
41
|
+
* {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip
|
|
42
|
+
* the challenge on this device, same as {@link login}.
|
|
43
|
+
*/
|
|
44
|
+
function googleLogin(clientId: string, idToken: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
32
45
|
/** Google OAuth login via server-side authorization code (public). */
|
|
33
46
|
function googleCodeLogin(clientId: string, code: string, redirectUri: string): Promise<AuthLoginResponse>;
|
|
34
47
|
/**
|
|
@@ -46,6 +59,9 @@ export declare namespace authKit {
|
|
|
46
59
|
* then link Apple from settings. **The same 409 can now come back from
|
|
47
60
|
* {@link googleLogin}** under the shared verified-to-verified linking policy.
|
|
48
61
|
*
|
|
62
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass
|
|
63
|
+
* `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
64
|
+
*
|
|
49
65
|
* @see AppleLoginOptions
|
|
50
66
|
*/
|
|
51
67
|
function appleLogin(clientId: string, identityToken: string, opts?: AppleLoginOptions): Promise<AuthLoginResponse>;
|
|
@@ -84,20 +100,46 @@ export declare namespace authKit {
|
|
|
84
100
|
redirectUrl: string;
|
|
85
101
|
accountData?: Record<string, any>;
|
|
86
102
|
}): Promise<MagicLinkSendResponse>;
|
|
87
|
-
/**
|
|
88
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Verify a magic link token and authenticate/create the user (public).
|
|
105
|
+
*
|
|
106
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
107
|
+
*
|
|
108
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
109
|
+
*/
|
|
110
|
+
function verifyMagicLink(clientId: string, token: string, trustedDeviceToken?: string): Promise<MagicLinkVerifyResponse>;
|
|
89
111
|
/** Send phone verification code (public). */
|
|
90
112
|
function sendPhoneCode(clientId: string, phoneNumber: string): Promise<PhoneSendCodeResponse>;
|
|
91
|
-
/**
|
|
92
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Verify phone verification code (public).
|
|
115
|
+
*
|
|
116
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
117
|
+
*
|
|
118
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
119
|
+
*/
|
|
120
|
+
function verifyPhoneCode(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string): Promise<PhoneVerifyResponse>;
|
|
93
121
|
/** Send a WhatsApp verification deep-link (public). */
|
|
94
122
|
function sendWhatsApp(clientId: string, body?: SendWhatsAppRequest): Promise<SendWhatsAppResponse>;
|
|
95
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
125
|
+
*
|
|
126
|
+
* Not gated by step-up MFA — this endpoint only confirms the code, it never issues a
|
|
127
|
+
* session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession}
|
|
128
|
+
* is the WhatsApp method that's gated.
|
|
129
|
+
*/
|
|
96
130
|
function verifyWhatsApp(clientId: string, token: string, phoneNumber: string): Promise<VerifyWhatsAppResponse>;
|
|
97
131
|
/** Poll WhatsApp verification status for a token (public). */
|
|
98
132
|
function getWhatsAppStatus(clientId: string, token: string): Promise<WhatsAppStatusResponse>;
|
|
99
|
-
/**
|
|
100
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Exchange a verified WhatsApp token for an Auth Kit session (public).
|
|
135
|
+
*
|
|
136
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is
|
|
137
|
+
* the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which
|
|
138
|
+
* never issues a session).
|
|
139
|
+
*
|
|
140
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
141
|
+
*/
|
|
142
|
+
function exchangeWhatsAppSession(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string): Promise<ExchangeWhatsAppSessionResponse>;
|
|
101
143
|
/** Send an SMS click-to-verify link (public). */
|
|
102
144
|
function sendSmsVerify(clientId: string, body: SendSmsVerifyRequest): Promise<SendSmsVerifyResponse>;
|
|
103
145
|
/** Verify an SMS click-to-verify token via API (public). */
|
package/dist/api/authKit.js
CHANGED
|
@@ -35,14 +35,27 @@ export var authKit;
|
|
|
35
35
|
return res;
|
|
36
36
|
}
|
|
37
37
|
authKit.login = login;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Register a new user (public).
|
|
40
|
+
*
|
|
41
|
+
* Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
|
|
42
|
+
* nothing to challenge against.
|
|
43
|
+
*/
|
|
39
44
|
async function register(clientId, data) {
|
|
40
45
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/register`, data);
|
|
41
46
|
}
|
|
42
47
|
authKit.register = register;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Google OAuth login via ID token (public).
|
|
50
|
+
*
|
|
51
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
52
|
+
*
|
|
53
|
+
* @param trustedDeviceToken - Optional. Pass a token previously returned by
|
|
54
|
+
* {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip
|
|
55
|
+
* the challenge on this device, same as {@link login}.
|
|
56
|
+
*/
|
|
57
|
+
async function googleLogin(clientId, idToken, trustedDeviceToken) {
|
|
58
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/google`, { idToken, trustedDeviceToken });
|
|
46
59
|
if (res.token) {
|
|
47
60
|
setBearerToken(res.token);
|
|
48
61
|
invalidateCache();
|
|
@@ -75,6 +88,9 @@ export var authKit;
|
|
|
75
88
|
* then link Apple from settings. **The same 409 can now come back from
|
|
76
89
|
* {@link googleLogin}** under the shared verified-to-verified linking policy.
|
|
77
90
|
*
|
|
91
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass
|
|
92
|
+
* `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
93
|
+
*
|
|
78
94
|
* @see AppleLoginOptions
|
|
79
95
|
*/
|
|
80
96
|
async function appleLogin(clientId, identityToken, opts) {
|
|
@@ -138,9 +154,15 @@ export var authKit;
|
|
|
138
154
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/magic-link/send`, data);
|
|
139
155
|
}
|
|
140
156
|
authKit.sendMagicLink = sendMagicLink;
|
|
141
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Verify a magic link token and authenticate/create the user (public).
|
|
159
|
+
*
|
|
160
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
161
|
+
*
|
|
162
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
163
|
+
*/
|
|
164
|
+
async function verifyMagicLink(clientId, token, trustedDeviceToken) {
|
|
165
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/magic-link/verify`, { token, trustedDeviceToken });
|
|
144
166
|
if (res.token) {
|
|
145
167
|
setBearerToken(res.token);
|
|
146
168
|
invalidateCache();
|
|
@@ -153,9 +175,15 @@ export var authKit;
|
|
|
153
175
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/phone/send-code`, { phoneNumber });
|
|
154
176
|
}
|
|
155
177
|
authKit.sendPhoneCode = sendPhoneCode;
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Verify phone verification code (public).
|
|
180
|
+
*
|
|
181
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
182
|
+
*
|
|
183
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
184
|
+
*/
|
|
185
|
+
async function verifyPhoneCode(clientId, phoneNumber, code, trustedDeviceToken) {
|
|
186
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/phone/verify`, { phoneNumber, code, trustedDeviceToken });
|
|
159
187
|
setBearerToken(res.token);
|
|
160
188
|
invalidateCache();
|
|
161
189
|
return res;
|
|
@@ -166,7 +194,13 @@ export var authKit;
|
|
|
166
194
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/send`, body);
|
|
167
195
|
}
|
|
168
196
|
authKit.sendWhatsApp = sendWhatsApp;
|
|
169
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
199
|
+
*
|
|
200
|
+
* Not gated by step-up MFA — this endpoint only confirms the code, it never issues a
|
|
201
|
+
* session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession}
|
|
202
|
+
* is the WhatsApp method that's gated.
|
|
203
|
+
*/
|
|
170
204
|
async function verifyWhatsApp(clientId, token, phoneNumber) {
|
|
171
205
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/verify`, { token, phoneNumber });
|
|
172
206
|
}
|
|
@@ -177,9 +211,17 @@ export var authKit;
|
|
|
177
211
|
return request(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/status?token=${encodedToken}`);
|
|
178
212
|
}
|
|
179
213
|
authKit.getWhatsAppStatus = getWhatsAppStatus;
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Exchange a verified WhatsApp token for an Auth Kit session (public).
|
|
216
|
+
*
|
|
217
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is
|
|
218
|
+
* the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which
|
|
219
|
+
* never issues a session).
|
|
220
|
+
*
|
|
221
|
+
* @param trustedDeviceToken - Optional. See {@link login}.
|
|
222
|
+
*/
|
|
223
|
+
async function exchangeWhatsAppSession(clientId, token, sessionKey, trustedDeviceToken) {
|
|
224
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/exchange-session`, { token, sessionKey, trustedDeviceToken });
|
|
183
225
|
setBearerToken(res.token);
|
|
184
226
|
invalidateCache();
|
|
185
227
|
return res;
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.15 | Generated: 2026-07-29T17:46:34.198Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -170,6 +170,9 @@ Replace or augment globally applied custom headers.
|
|
|
170
170
|
**setBearerToken**(token: string | undefined) → `void`
|
|
171
171
|
Allows setting the bearerToken at runtime (e.g. after login/logout). Clears the HTTP cache whenever the token actually changes so that stale user-scoped responses (e.g. /account/profile) are not served after a login or logout event.
|
|
172
172
|
|
|
173
|
+
**getBearerToken**() → `string | undefined`
|
|
174
|
+
Returns the bearer token currently held by the SDK, or `undefined` if none is set. In proxy mode, credentials are held by the parent frame, not the local SDK, so this returns `undefined` even when the caller is authenticated.
|
|
175
|
+
|
|
173
176
|
**getBaseURL**() → `string | null`
|
|
174
177
|
Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
|
|
175
178
|
|
|
@@ -3104,6 +3107,10 @@ interface AppleLoginOptions {
|
|
|
3104
3107
|
* these again, and never inside the token. Forwarded so the server can persist the
|
|
3105
3108
|
* display name on first account creation. Treated as untrusted (never used for identity).
|
|
3106
3109
|
userInfo?: { email?: string; name?: string }
|
|
3110
|
+
* A previously-issued trusted-device token (from a prior MFA `challenge/verify`
|
|
3111
|
+
* or `challenge/recovery-code` response). If still valid, the server skips any
|
|
3112
|
+
* step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
|
|
3113
|
+
trustedDeviceToken?: string
|
|
3107
3114
|
}
|
|
3108
3115
|
```
|
|
3109
3116
|
|
|
@@ -3778,6 +3785,21 @@ interface Collection {
|
|
|
3778
3785
|
portalUrl?: string // URL for the collection's portal (if applicable)
|
|
3779
3786
|
allowAutoGenerateClaims?: boolean
|
|
3780
3787
|
defaultAuthKitId: string // default auth kit for this collection, used for auth
|
|
3788
|
+
admin?: {
|
|
3789
|
+
* Redirect behavior for plain collection-level scans (a short link with
|
|
3790
|
+
* no product/serial code in the path, e.g. `https://.../c/shortId`).
|
|
3791
|
+
* Unset means such links always go to the normal collection page.
|
|
3792
|
+
redirect?: CollectionRedirectConfig
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
```
|
|
3796
|
+
|
|
3797
|
+
**CollectionRedirectConfig** (interface)
|
|
3798
|
+
```typescript
|
|
3799
|
+
interface CollectionRedirectConfig {
|
|
3800
|
+
mode: 'fixed' | 'dynamic' | 'deep'
|
|
3801
|
+
url?: string
|
|
3802
|
+
template?: string
|
|
3781
3803
|
}
|
|
3782
3804
|
```
|
|
3783
3805
|
|
|
@@ -8620,16 +8642,16 @@ Gets current account information for the logged in user. Returns user, owner, ac
|
|
|
8620
8642
|
Login with email + password (public). When the client's MFA policy requires a step-up, the server returns **403 `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in `err.details` (see {@link MfaRequiredDetails}). Route the caller to {@link mfaChallengeSend} on that error; this method's return type is unchanged. returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with `trustDevice: true`), pass it here to skip the challenge entirely as long as it's still valid. If it's revoked/expired, the server silently falls back to requiring a fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
8621
8643
|
|
|
8622
8644
|
**register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
|
|
8623
|
-
Register a new user (public).
|
|
8645
|
+
Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against.
|
|
8624
8646
|
|
|
8625
|
-
**googleLogin**(clientId: string, idToken: string) → `Promise<AuthLoginResponse>`
|
|
8626
|
-
Google OAuth login via ID token (public).
|
|
8647
|
+
**googleLogin**(clientId: string, idToken: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
8648
|
+
Google OAuth login via ID token (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip the challenge on this device, same as {@link login}.
|
|
8627
8649
|
|
|
8628
8650
|
**googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
|
|
8629
8651
|
Google OAuth login via server-side authorization code (public).
|
|
8630
8652
|
|
|
8631
8653
|
**appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
|
|
8632
|
-
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy.
|
|
8654
|
+
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
8633
8655
|
|
|
8634
8656
|
**refreshToken**(clientId: string, refreshToken: string) → `Promise<RefreshResponse>`
|
|
8635
8657
|
Exchange a refresh token for a fresh access token (public — the refresh token IS the credential). **Native sessions only**; refresh tokens are issued only when the host opted in via `initializeApi({ platform: 'native' })`. On success the new access token is stored automatically (`setBearerToken`). The returned `refreshToken` is **rotated** — the caller must persist it and discard the old one before refreshing again. ⚠️ **Single-use, no retry, serialize calls.** This method issues exactly one request and never retries: replaying a consumed refresh token triggers `REFRESH_TOKEN_REUSE_DETECTED` (the whole session family is revoked). The caller is responsible for ensuring only one refresh is in flight at a time (e.g. across tabs or resume events). Errors (thrown as `SmartlinksApiError`, read via `err.errorCode`): `MISSING_REFRESH_TOKEN` (400), `INVALID_REFRESH_TOKEN` (401), `REFRESH_TOKEN_REUSE_DETECTED` (401) — the last two mean a hard logout.
|
|
@@ -8640,26 +8662,26 @@ Revoke a refresh token's entire family server-side (that device's whole rotation
|
|
|
8640
8662
|
**sendMagicLink**(clientId: string, data: { email: string; redirectUrl: string; accountData?: Record<string, any> }) → `Promise<MagicLinkSendResponse>`
|
|
8641
8663
|
Send a magic link email to the user (public).
|
|
8642
8664
|
|
|
8643
|
-
**verifyMagicLink**(clientId: string, token: string) → `Promise<MagicLinkVerifyResponse>`
|
|
8644
|
-
Verify a magic link token and authenticate/create the user (public).
|
|
8665
|
+
**verifyMagicLink**(clientId: string, token: string, trustedDeviceToken?: string) → `Promise<MagicLinkVerifyResponse>`
|
|
8666
|
+
Verify a magic link token and authenticate/create the user (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
8645
8667
|
|
|
8646
8668
|
**sendPhoneCode**(clientId: string, phoneNumber: string) → `Promise<PhoneSendCodeResponse>`
|
|
8647
8669
|
Send phone verification code (public).
|
|
8648
8670
|
|
|
8649
|
-
**verifyPhoneCode**(clientId: string, phoneNumber: string, code: string) → `Promise<PhoneVerifyResponse>`
|
|
8650
|
-
Verify phone verification code (public).
|
|
8671
|
+
**verifyPhoneCode**(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string) → `Promise<PhoneVerifyResponse>`
|
|
8672
|
+
Verify phone verification code (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
8651
8673
|
|
|
8652
8674
|
**sendWhatsApp**(clientId: string, body: SendWhatsAppRequest = {}) → `Promise<SendWhatsAppResponse>`
|
|
8653
8675
|
Send a WhatsApp verification deep-link (public).
|
|
8654
8676
|
|
|
8655
8677
|
**verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
|
|
8656
|
-
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8678
|
+
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). Not gated by step-up MFA — this endpoint only confirms the code, it never issues a session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession} is the WhatsApp method that's gated.
|
|
8657
8679
|
|
|
8658
8680
|
**getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
|
|
8659
8681
|
Poll WhatsApp verification status for a token (public).
|
|
8660
8682
|
|
|
8661
|
-
**exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string) → `Promise<ExchangeWhatsAppSessionResponse>`
|
|
8662
|
-
Exchange a verified WhatsApp token for an Auth Kit session (public).
|
|
8683
|
+
**exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string) → `Promise<ExchangeWhatsAppSessionResponse>`
|
|
8684
|
+
Exchange a verified WhatsApp token for an Auth Kit session (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which never issues a session).
|
|
8663
8685
|
|
|
8664
8686
|
**sendSmsVerify**(clientId: string, body: SendSmsVerifyRequest) → `Promise<SendSmsVerifyResponse>`
|
|
8665
8687
|
Send an SMS click-to-verify link (public).
|
package/dist/docs/auth-kit.md
CHANGED
|
@@ -226,6 +226,7 @@ const session = await authKit.appleLogin(clientId, appleIdentityToken, {
|
|
|
226
226
|
// All optional:
|
|
227
227
|
nonce, // raw nonce, if you used nonce binding
|
|
228
228
|
userInfo: { name, email }, // first authorization callback ONLY — Apple never resends it
|
|
229
|
+
trustedDeviceToken, // skip an MFA step-up challenge on a recognized device — see "Step-up MFA" below
|
|
229
230
|
});
|
|
230
231
|
// session.isNewUser and session.expiresAt (ms epoch) are populated by this endpoint.
|
|
231
232
|
```
|
|
@@ -307,16 +308,32 @@ clearPersistedTokens();
|
|
|
307
308
|
## Step-up MFA (Phase 1)
|
|
308
309
|
|
|
309
310
|
Backend-only for now — **no admin-console UI and no challenge UI ship with this pass.**
|
|
310
|
-
Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys
|
|
311
|
-
deferred to a later phase; don't build against them yet.
|
|
311
|
+
Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys as
|
|
312
|
+
*factors* are deferred to a later phase; don't build against them yet.
|
|
312
313
|
|
|
313
|
-
The step-up gate
|
|
314
|
-
`verifyMagicLink` never return `MFA_REQUIRED`, regardless of client config.
|
|
314
|
+
The step-up gate applies to **every login method that issues a session**:
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
`login()`
|
|
319
|
-
|
|
316
|
+
| Method | Gated? |
|
|
317
|
+
|---|---|
|
|
318
|
+
| `login()` | ✅ |
|
|
319
|
+
| `googleLogin()` | ✅ |
|
|
320
|
+
| `appleLogin()` | ✅ — pass `opts.trustedDeviceToken` |
|
|
321
|
+
| `verifyPhoneCode()` | ✅ |
|
|
322
|
+
| `verifyMagicLink()` | ✅ |
|
|
323
|
+
| `exchangeWhatsAppSession()` | ✅ — **this is the WhatsApp method that needs a trusted-device token, not `verifyWhatsApp()`** |
|
|
324
|
+
| `verifyWhatsApp()` | ❌ — only confirms the code, never issues a session, so there's nothing to gate |
|
|
325
|
+
| `register()` | ❌ — a brand-new user has no enrolled factors to challenge against |
|
|
326
|
+
| `googleCodeLogin()` | ❌ — no `/auth/google-code` route exists server-side |
|
|
327
|
+
|
|
328
|
+
All six gated methods accept an optional `trustedDeviceToken` param (the last positional
|
|
329
|
+
argument, or `opts.trustedDeviceToken` for `appleLogin()`) — same purpose everywhere: skip
|
|
330
|
+
the challenge on a device the user already verified.
|
|
331
|
+
|
|
332
|
+
### Handling `MFA_REQUIRED`
|
|
333
|
+
|
|
334
|
+
Every gated method's return type is unchanged. When the client's MFA policy requires a
|
|
335
|
+
step-up, the server returns 403 instead of a session, and the method throws — identically
|
|
336
|
+
for all six:
|
|
320
337
|
|
|
321
338
|
```ts
|
|
322
339
|
import { authKit, SmartlinksApiError } from '@proveanything/smartlinks';
|
|
@@ -362,10 +379,15 @@ above — same secure storage, same persist-before-next-call discipline:
|
|
|
362
379
|
// 1) Persist trustedDeviceToken from a successful challenge (trustDevice: true)
|
|
363
380
|
persistTrustedDeviceToken(session.trustedDeviceToken, session.trustedDeviceExpiresAt);
|
|
364
381
|
|
|
365
|
-
// 2) Send it on every subsequent
|
|
382
|
+
// 2) Send it on every subsequent call to ANY of the six gated methods — if still valid,
|
|
383
|
+
// the challenge is skipped entirely. It isn't tied to which method the user challenged
|
|
384
|
+
// through, so "remember this device" works no matter which login method they pick next.
|
|
366
385
|
const session = await authKit.login(clientId, email, password, storedTrustedDeviceToken);
|
|
386
|
+
const session = await authKit.googleLogin(clientId, idToken, storedTrustedDeviceToken);
|
|
387
|
+
const session = await authKit.exchangeWhatsAppSession(clientId, waToken, sessionKey, storedTrustedDeviceToken);
|
|
388
|
+
// ...and so on for appleLogin (via opts), verifyPhoneCode, verifyMagicLink.
|
|
367
389
|
// If it's revoked/expired, the server silently falls back to requiring a fresh challenge —
|
|
368
|
-
//
|
|
390
|
+
// the method just throws MFA_REQUIRED again, no special-case handling needed.
|
|
369
391
|
|
|
370
392
|
// 3) Let users audit/revoke devices from a Settings screen
|
|
371
393
|
const { devices } = await authKit.listTrustedDevices(clientId);
|
|
@@ -404,7 +426,7 @@ await authKit.removeMfaFactor(clientId, 'sms', password);
|
|
|
404
426
|
|
|
405
427
|
| `errorCode` | HTTP | Meaning / client action |
|
|
406
428
|
|---|---|---|
|
|
407
|
-
| `MFA_REQUIRED` | 403 | From
|
|
429
|
+
| `MFA_REQUIRED` | 403 | From any of the six gated login methods — see above |
|
|
408
430
|
| `INVALID_MFA_CODE` | 401 | Wrong code — let the user retry (attempts are capped) |
|
|
409
431
|
| `MFA_TOO_MANY_ATTEMPTS` | 429 | 5 wrong attempts — the challenge is burned; restart from `login()` |
|
|
410
432
|
| `MFA_FACTOR_NOT_ENROLLED` | 400 | Requested factor isn't enrolled — programming error if the UI only offers `availableFactors` |
|
package/dist/http.d.ts
CHANGED
|
@@ -49,6 +49,12 @@ export declare function setExtraHeaders(headers: Record<string, string>): void;
|
|
|
49
49
|
* login or logout event.
|
|
50
50
|
*/
|
|
51
51
|
export declare function setBearerToken(token: string | undefined): void;
|
|
52
|
+
/**
|
|
53
|
+
* Returns the bearer token currently held by the SDK, or `undefined` if none is set.
|
|
54
|
+
* In proxy mode, credentials are held by the parent frame, not the local SDK,
|
|
55
|
+
* so this returns `undefined` even when the caller is authenticated.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getBearerToken(): string | undefined;
|
|
52
58
|
/**
|
|
53
59
|
* Get the currently configured API base URL.
|
|
54
60
|
* Returns null if initializeApi() has not been called yet.
|
package/dist/http.js
CHANGED
|
@@ -466,6 +466,14 @@ export function setBearerToken(token) {
|
|
|
466
466
|
if (cachePersistence !== 'none')
|
|
467
467
|
idbClear().catch(() => { });
|
|
468
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Returns the bearer token currently held by the SDK, or `undefined` if none is set.
|
|
471
|
+
* In proxy mode, credentials are held by the parent frame, not the local SDK,
|
|
472
|
+
* so this returns `undefined` even when the caller is authenticated.
|
|
473
|
+
*/
|
|
474
|
+
export function getBearerToken() {
|
|
475
|
+
return bearerToken;
|
|
476
|
+
}
|
|
469
477
|
/**
|
|
470
478
|
* Get the currently configured API base URL.
|
|
471
479
|
* Returns null if initializeApi() has not been called yet.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken } from "./http";
|
|
1
|
+
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
|
|
2
2
|
export * from "./api";
|
|
3
3
|
export * from "./types";
|
|
4
4
|
export { iframe } from "./iframe";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
// Top-level entrypoint of the npm package. Re-export initializeApi + all namespaces.
|
|
3
|
-
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken } from "./http";
|
|
3
|
+
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
|
|
4
4
|
export * from "./api";
|
|
5
5
|
export * from "./types";
|
|
6
6
|
// Iframe namespace
|
package/dist/openapi.yaml
CHANGED
|
@@ -8291,7 +8291,7 @@ paths:
|
|
|
8291
8291
|
post:
|
|
8292
8292
|
tags:
|
|
8293
8293
|
- authKit
|
|
8294
|
-
summary:
|
|
8294
|
+
summary: authKit.appleLogin
|
|
8295
8295
|
operationId: authKit_appleLogin
|
|
8296
8296
|
security: []
|
|
8297
8297
|
parameters:
|
|
@@ -18709,6 +18709,8 @@ components:
|
|
|
18709
18709
|
userInfo:
|
|
18710
18710
|
type: object
|
|
18711
18711
|
additionalProperties: true
|
|
18712
|
+
trustedDeviceToken:
|
|
18713
|
+
type: string
|
|
18712
18714
|
MfaRequiredDetails:
|
|
18713
18715
|
type: object
|
|
18714
18716
|
properties:
|
|
@@ -19723,6 +19725,11 @@ components:
|
|
|
19723
19725
|
type: boolean
|
|
19724
19726
|
defaultAuthKitId:
|
|
19725
19727
|
type: string
|
|
19728
|
+
admin:
|
|
19729
|
+
type: object
|
|
19730
|
+
additionalProperties: true
|
|
19731
|
+
redirect:
|
|
19732
|
+
$ref: "#/components/schemas/CollectionRedirectConfig"
|
|
19726
19733
|
required:
|
|
19727
19734
|
- id
|
|
19728
19735
|
- title
|
|
@@ -19737,6 +19744,21 @@ components:
|
|
|
19737
19744
|
- roles
|
|
19738
19745
|
- shortId
|
|
19739
19746
|
- defaultAuthKitId
|
|
19747
|
+
CollectionRedirectConfig:
|
|
19748
|
+
type: object
|
|
19749
|
+
properties:
|
|
19750
|
+
mode:
|
|
19751
|
+
type: string
|
|
19752
|
+
enum:
|
|
19753
|
+
- fixed
|
|
19754
|
+
- dynamic
|
|
19755
|
+
- deep
|
|
19756
|
+
url:
|
|
19757
|
+
type: string
|
|
19758
|
+
template:
|
|
19759
|
+
type: string
|
|
19760
|
+
required:
|
|
19761
|
+
- mode
|
|
19740
19762
|
HubAvailabilityResponse:
|
|
19741
19763
|
type: object
|
|
19742
19764
|
properties:
|
package/dist/types/authKit.d.ts
CHANGED
|
@@ -112,6 +112,12 @@ export interface AppleLoginOptions {
|
|
|
112
112
|
email?: string;
|
|
113
113
|
name?: string;
|
|
114
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* A previously-issued trusted-device token (from a prior MFA `challenge/verify`
|
|
117
|
+
* or `challenge/recovery-code` response). If still valid, the server skips any
|
|
118
|
+
* step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
|
|
119
|
+
*/
|
|
120
|
+
trustedDeviceToken?: string;
|
|
115
121
|
}
|
|
116
122
|
/**
|
|
117
123
|
* Server-defined error codes returned by AuthKit federated-login endpoints
|
|
@@ -121,10 +127,10 @@ export interface AppleLoginOptions {
|
|
|
121
127
|
*/
|
|
122
128
|
export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED';
|
|
123
129
|
/**
|
|
124
|
-
* Details carried on the `MFA_REQUIRED` error thrown by
|
|
125
|
-
* client's MFA policy requires a step-up. Surfaced as
|
|
126
|
-
* `err.errorResponse.details`) on the `SmartlinksApiError` —
|
|
127
|
-
* this case, it throws a 403 instead.
|
|
130
|
+
* Details carried on the `MFA_REQUIRED` error thrown by any session-issuing login method
|
|
131
|
+
* (see the gate list above) when the client's MFA policy requires a step-up. Surfaced as
|
|
132
|
+
* `err.details` (or `err.errorResponse.details`) on the `SmartlinksApiError` — the login
|
|
133
|
+
* method never resolves in this case, it throws a 403 instead.
|
|
128
134
|
*/
|
|
129
135
|
export interface MfaRequiredDetails {
|
|
130
136
|
/** Short-lived (10 min) and single-use. Pass it to every challenge/finalize call below. */
|
|
@@ -156,7 +162,10 @@ export interface MfaFinalizeResponse extends AuthLoginResponse {
|
|
|
156
162
|
/**
|
|
157
163
|
* Present only when the challenge was completed with `trustDevice: true`. Persist it
|
|
158
164
|
* alongside the refresh token (same secure storage) and send it as `trustedDeviceToken`
|
|
159
|
-
* on future
|
|
165
|
+
* on future calls to any gated login method ({@link authKit.login},
|
|
166
|
+
* {@link authKit.googleLogin}, {@link authKit.appleLogin}, {@link authKit.verifyPhoneCode},
|
|
167
|
+
* {@link authKit.verifyMagicLink}, {@link authKit.exchangeWhatsAppSession}) to skip the
|
|
168
|
+
* challenge on this device — it isn't tied to which method the user challenged through.
|
|
160
169
|
*/
|
|
161
170
|
trustedDeviceToken?: string;
|
|
162
171
|
trustedDeviceExpiresAt?: string;
|
|
@@ -199,7 +208,7 @@ export interface TrustedDevice {
|
|
|
199
208
|
* Server-defined error codes for the step-up MFA flow, surfaced via
|
|
200
209
|
* `SmartlinksApiError.errorCode`.
|
|
201
210
|
*
|
|
202
|
-
* - `MFA_REQUIRED` (403) — from
|
|
211
|
+
* - `MFA_REQUIRED` (403) — from any session-issuing login method; see {@link MfaRequiredDetails}.
|
|
203
212
|
* - `INVALID_MFA_CODE` (401) — wrong code; let the user retry (attempts are capped, see next).
|
|
204
213
|
* - `MFA_TOO_MANY_ATTEMPTS` (429) — 5 wrong attempts; the challenge is burned, restart from `login()`.
|
|
205
214
|
* - `MFA_FACTOR_NOT_ENROLLED` (400) — requested factor isn't enrolled (or disabled for the
|
|
@@ -58,6 +58,29 @@ export interface Collection {
|
|
|
58
58
|
/** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
|
|
59
59
|
allowAutoGenerateClaims?: boolean;
|
|
60
60
|
defaultAuthKitId: string;
|
|
61
|
+
/** Admin-configured collection behavior not exposed on public reads */
|
|
62
|
+
admin?: {
|
|
63
|
+
/**
|
|
64
|
+
* Redirect behavior for plain collection-level scans (a short link with
|
|
65
|
+
* no product/serial code in the path, e.g. `https://.../c/shortId`).
|
|
66
|
+
* Unset means such links always go to the normal collection page.
|
|
67
|
+
*/
|
|
68
|
+
redirect?: CollectionRedirectConfig;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Redirect rule for plain collection-level scans.
|
|
73
|
+
* - `fixed` / `dynamic` - redirect the entire collection to one URL/template,
|
|
74
|
+
* defined right here via `url`/`template`.
|
|
75
|
+
* - `deep` - defer to the collection's product-level, facet-rule, and
|
|
76
|
+
* collection-wide redirect config (checked in that order) instead.
|
|
77
|
+
*/
|
|
78
|
+
export interface CollectionRedirectConfig {
|
|
79
|
+
mode: 'fixed' | 'dynamic' | 'deep';
|
|
80
|
+
/** Required when mode === 'fixed' */
|
|
81
|
+
url?: string;
|
|
82
|
+
/** Mustache template, required when mode === 'dynamic' */
|
|
83
|
+
template?: string;
|
|
61
84
|
}
|
|
62
85
|
export type CollectionResponse = Collection;
|
|
63
86
|
export type CollectionCreateRequest = Omit<Collection, 'id' | 'shortId'>;
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.15 | Generated: 2026-07-29T17:46:34.198Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -170,6 +170,9 @@ Replace or augment globally applied custom headers.
|
|
|
170
170
|
**setBearerToken**(token: string | undefined) → `void`
|
|
171
171
|
Allows setting the bearerToken at runtime (e.g. after login/logout). Clears the HTTP cache whenever the token actually changes so that stale user-scoped responses (e.g. /account/profile) are not served after a login or logout event.
|
|
172
172
|
|
|
173
|
+
**getBearerToken**() → `string | undefined`
|
|
174
|
+
Returns the bearer token currently held by the SDK, or `undefined` if none is set. In proxy mode, credentials are held by the parent frame, not the local SDK, so this returns `undefined` even when the caller is authenticated.
|
|
175
|
+
|
|
173
176
|
**getBaseURL**() → `string | null`
|
|
174
177
|
Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
|
|
175
178
|
|
|
@@ -3104,6 +3107,10 @@ interface AppleLoginOptions {
|
|
|
3104
3107
|
* these again, and never inside the token. Forwarded so the server can persist the
|
|
3105
3108
|
* display name on first account creation. Treated as untrusted (never used for identity).
|
|
3106
3109
|
userInfo?: { email?: string; name?: string }
|
|
3110
|
+
* A previously-issued trusted-device token (from a prior MFA `challenge/verify`
|
|
3111
|
+
* or `challenge/recovery-code` response). If still valid, the server skips any
|
|
3112
|
+
* step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
|
|
3113
|
+
trustedDeviceToken?: string
|
|
3107
3114
|
}
|
|
3108
3115
|
```
|
|
3109
3116
|
|
|
@@ -3778,6 +3785,21 @@ interface Collection {
|
|
|
3778
3785
|
portalUrl?: string // URL for the collection's portal (if applicable)
|
|
3779
3786
|
allowAutoGenerateClaims?: boolean
|
|
3780
3787
|
defaultAuthKitId: string // default auth kit for this collection, used for auth
|
|
3788
|
+
admin?: {
|
|
3789
|
+
* Redirect behavior for plain collection-level scans (a short link with
|
|
3790
|
+
* no product/serial code in the path, e.g. `https://.../c/shortId`).
|
|
3791
|
+
* Unset means such links always go to the normal collection page.
|
|
3792
|
+
redirect?: CollectionRedirectConfig
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
```
|
|
3796
|
+
|
|
3797
|
+
**CollectionRedirectConfig** (interface)
|
|
3798
|
+
```typescript
|
|
3799
|
+
interface CollectionRedirectConfig {
|
|
3800
|
+
mode: 'fixed' | 'dynamic' | 'deep'
|
|
3801
|
+
url?: string
|
|
3802
|
+
template?: string
|
|
3781
3803
|
}
|
|
3782
3804
|
```
|
|
3783
3805
|
|
|
@@ -8620,16 +8642,16 @@ Gets current account information for the logged in user. Returns user, owner, ac
|
|
|
8620
8642
|
Login with email + password (public). When the client's MFA policy requires a step-up, the server returns **403 `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in `err.details` (see {@link MfaRequiredDetails}). Route the caller to {@link mfaChallengeSend} on that error; this method's return type is unchanged. returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with `trustDevice: true`), pass it here to skip the challenge entirely as long as it's still valid. If it's revoked/expired, the server silently falls back to requiring a fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
8621
8643
|
|
|
8622
8644
|
**register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
|
|
8623
|
-
Register a new user (public).
|
|
8645
|
+
Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against.
|
|
8624
8646
|
|
|
8625
|
-
**googleLogin**(clientId: string, idToken: string) → `Promise<AuthLoginResponse>`
|
|
8626
|
-
Google OAuth login via ID token (public).
|
|
8647
|
+
**googleLogin**(clientId: string, idToken: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
8648
|
+
Google OAuth login via ID token (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip the challenge on this device, same as {@link login}.
|
|
8627
8649
|
|
|
8628
8650
|
**googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
|
|
8629
8651
|
Google OAuth login via server-side authorization code (public).
|
|
8630
8652
|
|
|
8631
8653
|
**appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
|
|
8632
|
-
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy.
|
|
8654
|
+
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
8633
8655
|
|
|
8634
8656
|
**refreshToken**(clientId: string, refreshToken: string) → `Promise<RefreshResponse>`
|
|
8635
8657
|
Exchange a refresh token for a fresh access token (public — the refresh token IS the credential). **Native sessions only**; refresh tokens are issued only when the host opted in via `initializeApi({ platform: 'native' })`. On success the new access token is stored automatically (`setBearerToken`). The returned `refreshToken` is **rotated** — the caller must persist it and discard the old one before refreshing again. ⚠️ **Single-use, no retry, serialize calls.** This method issues exactly one request and never retries: replaying a consumed refresh token triggers `REFRESH_TOKEN_REUSE_DETECTED` (the whole session family is revoked). The caller is responsible for ensuring only one refresh is in flight at a time (e.g. across tabs or resume events). Errors (thrown as `SmartlinksApiError`, read via `err.errorCode`): `MISSING_REFRESH_TOKEN` (400), `INVALID_REFRESH_TOKEN` (401), `REFRESH_TOKEN_REUSE_DETECTED` (401) — the last two mean a hard logout.
|
|
@@ -8640,26 +8662,26 @@ Revoke a refresh token's entire family server-side (that device's whole rotation
|
|
|
8640
8662
|
**sendMagicLink**(clientId: string, data: { email: string; redirectUrl: string; accountData?: Record<string, any> }) → `Promise<MagicLinkSendResponse>`
|
|
8641
8663
|
Send a magic link email to the user (public).
|
|
8642
8664
|
|
|
8643
|
-
**verifyMagicLink**(clientId: string, token: string) → `Promise<MagicLinkVerifyResponse>`
|
|
8644
|
-
Verify a magic link token and authenticate/create the user (public).
|
|
8665
|
+
**verifyMagicLink**(clientId: string, token: string, trustedDeviceToken?: string) → `Promise<MagicLinkVerifyResponse>`
|
|
8666
|
+
Verify a magic link token and authenticate/create the user (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
8645
8667
|
|
|
8646
8668
|
**sendPhoneCode**(clientId: string, phoneNumber: string) → `Promise<PhoneSendCodeResponse>`
|
|
8647
8669
|
Send phone verification code (public).
|
|
8648
8670
|
|
|
8649
|
-
**verifyPhoneCode**(clientId: string, phoneNumber: string, code: string) → `Promise<PhoneVerifyResponse>`
|
|
8650
|
-
Verify phone verification code (public).
|
|
8671
|
+
**verifyPhoneCode**(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string) → `Promise<PhoneVerifyResponse>`
|
|
8672
|
+
Verify phone verification code (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
8651
8673
|
|
|
8652
8674
|
**sendWhatsApp**(clientId: string, body: SendWhatsAppRequest = {}) → `Promise<SendWhatsAppResponse>`
|
|
8653
8675
|
Send a WhatsApp verification deep-link (public).
|
|
8654
8676
|
|
|
8655
8677
|
**verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
|
|
8656
|
-
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8678
|
+
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). Not gated by step-up MFA — this endpoint only confirms the code, it never issues a session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession} is the WhatsApp method that's gated.
|
|
8657
8679
|
|
|
8658
8680
|
**getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
|
|
8659
8681
|
Poll WhatsApp verification status for a token (public).
|
|
8660
8682
|
|
|
8661
|
-
**exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string) → `Promise<ExchangeWhatsAppSessionResponse>`
|
|
8662
|
-
Exchange a verified WhatsApp token for an Auth Kit session (public).
|
|
8683
|
+
**exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string) → `Promise<ExchangeWhatsAppSessionResponse>`
|
|
8684
|
+
Exchange a verified WhatsApp token for an Auth Kit session (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which never issues a session).
|
|
8663
8685
|
|
|
8664
8686
|
**sendSmsVerify**(clientId: string, body: SendSmsVerifyRequest) → `Promise<SendSmsVerifyResponse>`
|
|
8665
8687
|
Send an SMS click-to-verify link (public).
|
package/docs/auth-kit.md
CHANGED
|
@@ -226,6 +226,7 @@ const session = await authKit.appleLogin(clientId, appleIdentityToken, {
|
|
|
226
226
|
// All optional:
|
|
227
227
|
nonce, // raw nonce, if you used nonce binding
|
|
228
228
|
userInfo: { name, email }, // first authorization callback ONLY — Apple never resends it
|
|
229
|
+
trustedDeviceToken, // skip an MFA step-up challenge on a recognized device — see "Step-up MFA" below
|
|
229
230
|
});
|
|
230
231
|
// session.isNewUser and session.expiresAt (ms epoch) are populated by this endpoint.
|
|
231
232
|
```
|
|
@@ -307,16 +308,32 @@ clearPersistedTokens();
|
|
|
307
308
|
## Step-up MFA (Phase 1)
|
|
308
309
|
|
|
309
310
|
Backend-only for now — **no admin-console UI and no challenge UI ship with this pass.**
|
|
310
|
-
Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys
|
|
311
|
-
deferred to a later phase; don't build against them yet.
|
|
311
|
+
Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys as
|
|
312
|
+
*factors* are deferred to a later phase; don't build against them yet.
|
|
312
313
|
|
|
313
|
-
The step-up gate
|
|
314
|
-
`verifyMagicLink` never return `MFA_REQUIRED`, regardless of client config.
|
|
314
|
+
The step-up gate applies to **every login method that issues a session**:
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
`login()`
|
|
319
|
-
|
|
316
|
+
| Method | Gated? |
|
|
317
|
+
|---|---|
|
|
318
|
+
| `login()` | ✅ |
|
|
319
|
+
| `googleLogin()` | ✅ |
|
|
320
|
+
| `appleLogin()` | ✅ — pass `opts.trustedDeviceToken` |
|
|
321
|
+
| `verifyPhoneCode()` | ✅ |
|
|
322
|
+
| `verifyMagicLink()` | ✅ |
|
|
323
|
+
| `exchangeWhatsAppSession()` | ✅ — **this is the WhatsApp method that needs a trusted-device token, not `verifyWhatsApp()`** |
|
|
324
|
+
| `verifyWhatsApp()` | ❌ — only confirms the code, never issues a session, so there's nothing to gate |
|
|
325
|
+
| `register()` | ❌ — a brand-new user has no enrolled factors to challenge against |
|
|
326
|
+
| `googleCodeLogin()` | ❌ — no `/auth/google-code` route exists server-side |
|
|
327
|
+
|
|
328
|
+
All six gated methods accept an optional `trustedDeviceToken` param (the last positional
|
|
329
|
+
argument, or `opts.trustedDeviceToken` for `appleLogin()`) — same purpose everywhere: skip
|
|
330
|
+
the challenge on a device the user already verified.
|
|
331
|
+
|
|
332
|
+
### Handling `MFA_REQUIRED`
|
|
333
|
+
|
|
334
|
+
Every gated method's return type is unchanged. When the client's MFA policy requires a
|
|
335
|
+
step-up, the server returns 403 instead of a session, and the method throws — identically
|
|
336
|
+
for all six:
|
|
320
337
|
|
|
321
338
|
```ts
|
|
322
339
|
import { authKit, SmartlinksApiError } from '@proveanything/smartlinks';
|
|
@@ -362,10 +379,15 @@ above — same secure storage, same persist-before-next-call discipline:
|
|
|
362
379
|
// 1) Persist trustedDeviceToken from a successful challenge (trustDevice: true)
|
|
363
380
|
persistTrustedDeviceToken(session.trustedDeviceToken, session.trustedDeviceExpiresAt);
|
|
364
381
|
|
|
365
|
-
// 2) Send it on every subsequent
|
|
382
|
+
// 2) Send it on every subsequent call to ANY of the six gated methods — if still valid,
|
|
383
|
+
// the challenge is skipped entirely. It isn't tied to which method the user challenged
|
|
384
|
+
// through, so "remember this device" works no matter which login method they pick next.
|
|
366
385
|
const session = await authKit.login(clientId, email, password, storedTrustedDeviceToken);
|
|
386
|
+
const session = await authKit.googleLogin(clientId, idToken, storedTrustedDeviceToken);
|
|
387
|
+
const session = await authKit.exchangeWhatsAppSession(clientId, waToken, sessionKey, storedTrustedDeviceToken);
|
|
388
|
+
// ...and so on for appleLogin (via opts), verifyPhoneCode, verifyMagicLink.
|
|
367
389
|
// If it's revoked/expired, the server silently falls back to requiring a fresh challenge —
|
|
368
|
-
//
|
|
390
|
+
// the method just throws MFA_REQUIRED again, no special-case handling needed.
|
|
369
391
|
|
|
370
392
|
// 3) Let users audit/revoke devices from a Settings screen
|
|
371
393
|
const { devices } = await authKit.listTrustedDevices(clientId);
|
|
@@ -404,7 +426,7 @@ await authKit.removeMfaFactor(clientId, 'sms', password);
|
|
|
404
426
|
|
|
405
427
|
| `errorCode` | HTTP | Meaning / client action |
|
|
406
428
|
|---|---|---|
|
|
407
|
-
| `MFA_REQUIRED` | 403 | From
|
|
429
|
+
| `MFA_REQUIRED` | 403 | From any of the six gated login methods — see above |
|
|
408
430
|
| `INVALID_MFA_CODE` | 401 | Wrong code — let the user retry (attempts are capped) |
|
|
409
431
|
| `MFA_TOO_MANY_ATTEMPTS` | 429 | 5 wrong attempts — the challenge is burned; restart from `login()` |
|
|
410
432
|
| `MFA_FACTOR_NOT_ENROLLED` | 400 | Requested factor isn't enrolled — programming error if the UI only offers `availableFactors` |
|
package/openapi.yaml
CHANGED
|
@@ -8291,7 +8291,7 @@ paths:
|
|
|
8291
8291
|
post:
|
|
8292
8292
|
tags:
|
|
8293
8293
|
- authKit
|
|
8294
|
-
summary:
|
|
8294
|
+
summary: authKit.appleLogin
|
|
8295
8295
|
operationId: authKit_appleLogin
|
|
8296
8296
|
security: []
|
|
8297
8297
|
parameters:
|
|
@@ -18709,6 +18709,8 @@ components:
|
|
|
18709
18709
|
userInfo:
|
|
18710
18710
|
type: object
|
|
18711
18711
|
additionalProperties: true
|
|
18712
|
+
trustedDeviceToken:
|
|
18713
|
+
type: string
|
|
18712
18714
|
MfaRequiredDetails:
|
|
18713
18715
|
type: object
|
|
18714
18716
|
properties:
|
|
@@ -19723,6 +19725,11 @@ components:
|
|
|
19723
19725
|
type: boolean
|
|
19724
19726
|
defaultAuthKitId:
|
|
19725
19727
|
type: string
|
|
19728
|
+
admin:
|
|
19729
|
+
type: object
|
|
19730
|
+
additionalProperties: true
|
|
19731
|
+
redirect:
|
|
19732
|
+
$ref: "#/components/schemas/CollectionRedirectConfig"
|
|
19726
19733
|
required:
|
|
19727
19734
|
- id
|
|
19728
19735
|
- title
|
|
@@ -19737,6 +19744,21 @@ components:
|
|
|
19737
19744
|
- roles
|
|
19738
19745
|
- shortId
|
|
19739
19746
|
- defaultAuthKitId
|
|
19747
|
+
CollectionRedirectConfig:
|
|
19748
|
+
type: object
|
|
19749
|
+
properties:
|
|
19750
|
+
mode:
|
|
19751
|
+
type: string
|
|
19752
|
+
enum:
|
|
19753
|
+
- fixed
|
|
19754
|
+
- dynamic
|
|
19755
|
+
- deep
|
|
19756
|
+
url:
|
|
19757
|
+
type: string
|
|
19758
|
+
template:
|
|
19759
|
+
type: string
|
|
19760
|
+
required:
|
|
19761
|
+
- mode
|
|
19740
19762
|
HubAvailabilityResponse:
|
|
19741
19763
|
type: object
|
|
19742
19764
|
properties:
|