@spfn/auth 0.3.0-beta.26 → 0.3.0-beta.27
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/README.md +49 -7
- package/dist/client-proof.js +3 -2
- package/dist/client-proof.js.map +1 -1
- package/dist/index.d.ts +194 -189
- package/dist/index.js +40 -40
- package/dist/index.js.map +1 -1
- package/dist/{machine-principals-CdEgxOB1.d.ts → machine-principals-BvRw8b8t.d.ts} +18 -0
- package/dist/server.d.ts +37 -4
- package/dist/server.js +210 -33
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,12 +104,16 @@ Import it for its side-effect (it self-registers); it must run before the proxy
|
|
|
104
104
|
// app/api/rpc/[routeName]/route.ts
|
|
105
105
|
import '@spfn/auth/nextjs/api'; // side-effect: registers auth interceptors
|
|
106
106
|
import { createRpcProxy } from '@spfn/core/nextjs/server';
|
|
107
|
-
import { authRouteMap } from '@spfn/auth';
|
|
108
107
|
import { routeMap } from '@/generated/route-map';
|
|
109
108
|
|
|
110
|
-
export const { GET, POST } = createRpcProxy({ routeMap
|
|
109
|
+
export const { GET, POST } = createRpcProxy({ routeMap });
|
|
111
110
|
```
|
|
112
111
|
|
|
112
|
+
No auth route map is merged: the generated `routeMap` carries the routes of every package
|
|
113
|
+
router the app router mounts with `.packages()`, `authRouter`'s included. `authRouteMap` is
|
|
114
|
+
still exported and `{ ...routeMap, ...authRouteMap }` is still harmless — the two hold the
|
|
115
|
+
same entries — but it is a no-op.
|
|
116
|
+
|
|
113
117
|
### 4. Run migrations
|
|
114
118
|
|
|
115
119
|
```bash
|
|
@@ -499,6 +503,31 @@ const answer = await authApi.pollDeviceAuth.call({ body: { deviceCode } });
|
|
|
499
503
|
// → { status: 'approved', userId, publicId, email?, phone?, passwordChangeRequired }
|
|
500
504
|
```
|
|
501
505
|
|
|
506
|
+
Or long-poll: send `waitMillis` and the server holds a pending request until the owner
|
|
507
|
+
answers or the wait runs out, so the device learns of an approval the moment it is made
|
|
508
|
+
instead of at its next tick.
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
let answer;
|
|
512
|
+
|
|
513
|
+
do
|
|
514
|
+
{
|
|
515
|
+
// Held up to 20s (the server's maxWaitMs caps it). A pending answer takes the time
|
|
516
|
+
// already waited off intervalMillis — 0 after a full wait, so ask again at once.
|
|
517
|
+
// An error ends the loop, as before.
|
|
518
|
+
answer = await authApi.pollDeviceAuth.call({ body: { deviceCode, waitMillis: 20_000 } });
|
|
519
|
+
|
|
520
|
+
if (answer.status === 'pending' && answer.intervalMillis > 0)
|
|
521
|
+
{
|
|
522
|
+
await new Promise(resolve => setTimeout(resolve, answer.intervalMillis));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
while (answer.status === 'pending');
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Keep the loop's sleep on `intervalMillis > 0`. It covers a server that answered without
|
|
529
|
+
waiting — an older one that ignores the field — so the loop never spins.
|
|
530
|
+
|
|
502
531
|
```typescript
|
|
503
532
|
// On the signed-in device — the user typed the code they read off the other screen.
|
|
504
533
|
const asking = await authApi.getDeviceAuthInfo.call({ body: { userCode } });
|
|
@@ -553,22 +582,36 @@ two ways in are indistinguishable.
|
|
|
553
582
|
who cannot authenticate, so `publicKey`, `keyId` and `fingerprint` carry length limits —
|
|
554
583
|
generous next to a real key (an RSA-2048 SPKI is 392 base64 characters against a 2048 limit)
|
|
555
584
|
and small next to the megabyte that would otherwise sit in a table no job clears.
|
|
585
|
+
- **A long poll holds no transaction.** The wait is route middleware in front of the poll's
|
|
586
|
+
`Transactional()`, so a waiting device does not pin a pooled connection, and the answer is
|
|
587
|
+
judged inside the transaction exactly as a poll without `waitMillis` is — same atomicity,
|
|
588
|
+
same database-error answers. Approve, deny and a global revocation wake a poll parked in the
|
|
589
|
+
same process after they commit. A poll parked on another instance re-reads its record every
|
|
590
|
+
second, so an approval committed elsewhere reaches it within about a second. A device that
|
|
591
|
+
hangs up mid-wait is not judged, so an approval it can no longer hear waits for its next poll;
|
|
592
|
+
at most three polls wait on one code at a time — a fourth is answered at once; and a server
|
|
593
|
+
that starts shutting down ends every wait with a pending answer rather than a cut connection.
|
|
556
594
|
- **Clock skew cannot affect this.** Every timestamp in the decision is the server's. The
|
|
557
595
|
`expiresAtMillis` in the start response is for the waiting device's countdown display, and
|
|
558
596
|
nothing the client believes about the time reaches the server's judgement.
|
|
559
597
|
|
|
560
|
-
|
|
561
|
-
|
|
598
|
+
Three knobs, resolved at lifecycle time rather than read per call — the first two are
|
|
599
|
+
announced to the waiting device in the start response:
|
|
562
600
|
|
|
563
601
|
```typescript
|
|
564
602
|
createAuthLifecycle({
|
|
565
603
|
deviceAuth: {
|
|
566
604
|
ttlMs: 10 * 60 * 1000, // how long a code lives. default 10 minutes
|
|
567
605
|
intervalMs: 5000, // poll interval the server asks for. default 5s
|
|
606
|
+
maxWaitMs: 20_000, // longest a long poll is held. default 20s
|
|
568
607
|
},
|
|
569
608
|
})
|
|
570
609
|
```
|
|
571
610
|
|
|
611
|
+
Keep `maxWaitMs` under the idle timeout of every proxy and load balancer in front of the
|
|
612
|
+
server. A long poll cut off by one reaches the device as a network error, not as a pending
|
|
613
|
+
answer — Google Cloud's load balancer closes a backend request at 30 seconds by default.
|
|
614
|
+
|
|
572
615
|
No job sweeps the table. Rows are judged by `expiresAt` whenever they are read or moved, so a
|
|
573
616
|
stale row authorizes nothing; it only keeps its user code out of circulation, and 31⁸ codes do
|
|
574
617
|
not run out.
|
|
@@ -3231,9 +3274,8 @@ export type AppRouter = typeof appRouter;
|
|
|
3231
3274
|
// app/api/rpc/[routeName]/route.ts
|
|
3232
3275
|
import '@spfn/auth/nextjs/api';
|
|
3233
3276
|
import { createRpcProxy } from '@spfn/core/nextjs/server';
|
|
3234
|
-
import {
|
|
3235
|
-
|
|
3236
|
-
export const { GET, POST } = createRpcProxy({ routeMap: { ...routeMap, ...authRouteMap } });
|
|
3277
|
+
import { routeMap } from '@/generated/route-map'; // already holds authRouter's routes
|
|
3278
|
+
export const { GET, POST } = createRpcProxy({ routeMap });
|
|
3237
3279
|
|
|
3238
3280
|
// any client component
|
|
3239
3281
|
import { authApi } from '@spfn/auth';
|
package/dist/client-proof.js
CHANGED
|
@@ -1419,7 +1419,7 @@ function isAppKind(kind) {
|
|
|
1419
1419
|
}
|
|
1420
1420
|
|
|
1421
1421
|
// src/server/client-proof/contract-bundle.ts
|
|
1422
|
-
var CONTRACT_VERSION = "0.13.
|
|
1422
|
+
var CONTRACT_VERSION = "0.13.1";
|
|
1423
1423
|
var CONTRACT_MAJOR = 0;
|
|
1424
1424
|
var CONTRACT_SUPPORTED_RANGE = ">=0.13.0 <0.14.0";
|
|
1425
1425
|
function required(name, type) {
|
|
@@ -1754,7 +1754,8 @@ var CONTRACT_TYPES = [
|
|
|
1754
1754
|
{
|
|
1755
1755
|
name: "PollDeviceAuthRequest",
|
|
1756
1756
|
fields: [
|
|
1757
|
-
required("deviceCode", "string")
|
|
1757
|
+
required("deviceCode", "string"),
|
|
1758
|
+
optional("waitMillis", "integer")
|
|
1758
1759
|
]
|
|
1759
1760
|
},
|
|
1760
1761
|
/**
|