@lushly-dev/afd-auth 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ const middleware = createAuthMiddleware(adapter, { exclude: ['public-cmd'] });
40
40
  | `unauthenticated` | `UNAUTHORIZED`, not retryable |
41
41
  | `loading` | `UNAUTHORIZED`, retryable |
42
42
  | `authenticated`, `session.expiresAt` at or before now (or not a valid date) | `TOKEN_EXPIRED`, retryable |
43
- | `authenticated` | sets `context.auth` and runs the command |
43
+ | `authenticated` | sets `context.auth`, fills `context.actor` from the session user and runs the command |
44
44
 
45
45
  - The built-in commands `auth-sign-in`, `auth-sign-out` and `auth-session-get`
46
46
  (`AUTH_COMMAND_NAMES`) always run, so a signed-out caller can sign in.
@@ -48,6 +48,10 @@ const middleware = createAuthMiddleware(adapter, { exclude: ['public-cmd'] });
48
48
  - The middleware sets or deletes `context.auth` on every call, excluded
49
49
  commands included. A handler never sees a `context.auth` that the caller
50
50
  supplied.
51
+ - With a verified session, a `human` (or missing) `context.actor` becomes
52
+ `{ kind: 'human', id: user.id, label: user.name }`. An `agent`, `service` or
53
+ `script` actor keeps its identity and gets `onBehalfOf: user.id`, replacing any
54
+ `onBehalfOf` the caller set.
51
55
  - Importing the package types `context.auth` as
52
56
  `AuthenticatedSessionState | undefined` through a `CommandContext` module
53
57
  augmentation.
@@ -187,15 +191,17 @@ callbacks, refresh of upstream OAuth tokens, simultaneous refresh-lock atomicity
187
191
  other browsers or Convex deployment behavior. Those deployment-specific checks
188
192
  remain tracked in [#362](https://github.com/lushly-dev/afd/issues/362).
189
193
 
190
- For an optional provider/browser validation run, provision a disposable staging
191
- application with its HTTPS origin and callback URL registered with the provider,
192
- a dedicated test account, test OAuth client configuration (or a dedicated Convex
193
- deployment) and a short session lifetime. Use two tabs in one fresh browser profile:
194
- complete OAuth through the actual callback, verify the adapter user/session in both
195
- tabs, trigger provider refresh across expiry, then sign out and verify both tabs
196
- and an independent session endpoint reject the previous session. Record deployment
197
- revision, provider/client version, cookie attributes, timing and each observed
198
- outcome without recording tokens. Revoke test sessions, delete the test account
199
- and disposable deployment, and close the profile. Missing configuration means
200
- **not run**; the local test cannot satisfy those checks. Production credentials
201
- are unnecessary for the default gate.
194
+ The additional [Better Auth HTTPS/OAuth fixture](hosted/better-auth/README.md)
195
+ uses a local test OAuth provider and SQLite-backed Better Auth. It exercises the
196
+ callback, Secure cookies and restart persistence without a hosted account. The
197
+ [Convex fixture](hosted/convex/README.md) bundles actual React/provider SDKs and
198
+ deployable backend functions; its local checks do not execute a Convex deployment.
199
+
200
+ After installing Chromium, run `pnpm test:hosted:local` from the repository root
201
+ on Node 24. It also runs the durable REST/view-state lifecycle. Separate opt-in
202
+ commands `test:hosted:better-auth` and `test:hosted:convex` require the fixture's
203
+ explicit disposable-service configuration and fail preflight when it is missing.
204
+ See the [hosted verification handoff](../../docs/operations/hosted-verification.md)
205
+ for local evidence, provisioning order, receipt requirements and cleanup. External
206
+ OAuth grants, deployment restart and actual Convex authentication remain pending
207
+ under #362; a local fixture pass does not close them.
@@ -28,7 +28,7 @@ export interface AuthMiddlewareOptions {
28
28
  * - Unauthenticated → failure with UNAUTHORIZED
29
29
  * - Loading → failure with UNAUTHORIZED (retryable)
30
30
  * - Authenticated but `expiresAt` reached → failure with TOKEN_EXPIRED (retryable)
31
- * - Authenticated → sets `context.auth` and calls next()
31
+ * - Authenticated → sets `context.auth`, fills `context.actor` from the session user and calls next()
32
32
  *
33
33
  * Excluded commands, and the built-in auth commands, always run. They see
34
34
  * `context.auth` only when the adapter holds an unexpired session.
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI9D,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,QAAQ,sBAAsB,CAAC;IACrC,UAAU,cAAc;QACvB;;;;;WAKG;QACH,IAAI,CAAC,EAAE,yBAAyB,CAAC;KACjC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GACjC,iBAAiB,CA6CnB"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAS,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIrE,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,QAAQ,sBAAsB,CAAC;IACrC,UAAU,cAAc;QACvB;;;;;WAKG;QACH,IAAI,CAAC,EAAE,yBAAyB,CAAC;KACjC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAgBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GACjC,iBAAiB,CA8CnB"}
@@ -4,13 +4,26 @@
4
4
  import { failure } from '@lushly-dev/afd-core';
5
5
  import { AUTH_COMMAND_NAMES } from './command-names.js';
6
6
  import { isSessionExpired } from './session-state.js';
7
+ /**
8
+ * The actor for a call made with a verified session. A human (or unset) actor becomes the
9
+ * session user (`id`, and `label` from the user's name). An agent, service or script keeps
10
+ * its own identity and acts on behalf of the session user; a caller-supplied `onBehalfOf`
11
+ * is replaced, so it can never name someone other than the verified user.
12
+ */
13
+ function actorForSession(actor, session) {
14
+ const { user } = session;
15
+ if (!actor || actor.kind === 'human') {
16
+ return { kind: 'human', id: user.id, ...(user.name ? { label: user.name } : {}) };
17
+ }
18
+ return { ...actor, onBehalfOf: user.id };
19
+ }
7
20
  /**
8
21
  * Create middleware that gates commands behind authentication.
9
22
  *
10
23
  * - Unauthenticated → failure with UNAUTHORIZED
11
24
  * - Loading → failure with UNAUTHORIZED (retryable)
12
25
  * - Authenticated but `expiresAt` reached → failure with TOKEN_EXPIRED (retryable)
13
- * - Authenticated → sets `context.auth` and calls next()
26
+ * - Authenticated → sets `context.auth`, fills `context.actor` from the session user and calls next()
14
27
  *
15
28
  * Excluded commands, and the built-in auth commands, always run. They see
16
29
  * `context.auth` only when the adapter holds an unexpired session.
@@ -23,6 +36,7 @@ export function createAuthMiddleware(adapter, options = {}) {
23
36
  // Overwrite or remove whatever the caller put in the context.
24
37
  if (verified) {
25
38
  context.auth = verified;
39
+ context.actor = actorForSession(context.actor, verified);
26
40
  }
27
41
  else {
28
42
  delete context.auth;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAwBtD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAoB,EACpB,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACnD,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GACb,KAAK,CAAC,MAAM,KAAK,eAAe,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAE1F,8DAA8D;QAC9D,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;QACzB,CAAC;aAAM,CAAC;YACP,OAAO,OAAO,CAAC,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC3C,OAAO,IAAI,EAAE,CAAC;QACf,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,iCAAiC;gBAC1C,UAAU,EAAE,mDAAmD;gBAC/D,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,qBAAqB;gBAC9B,UAAU,EACT,gGAAgG;gBACjG,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;YACd,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,yBAAyB;YAClC,UAAU,EAAE,kDAAkD;YAC9D,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAwBtD;;;;;GAKG;AACH,SAAS,eAAe,CAAC,KAAwB,EAAE,OAAkC;IACpF,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACnF,CAAC;IACD,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAoB,EACpB,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACnD,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GACb,KAAK,CAAC,MAAM,KAAK,eAAe,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAE1F,8DAA8D;QAC9D,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;YACxB,OAAO,CAAC,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACP,OAAO,OAAO,CAAC,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC3C,OAAO,IAAI,EAAE,CAAC;QACf,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,iCAAiC;gBAC1C,UAAU,EAAE,mDAAmD;gBAC/D,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,qBAAqB;gBAC9B,UAAU,EACT,gGAAgG;gBACjG,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;YACd,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,yBAAyB;YAClC,UAAU,EAAE,kDAAkD;YAC9D,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lushly-dev/afd-auth",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Provider-agnostic authentication adapter for AFD applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,14 +24,14 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@lushly-dev/afd-core": "2.1.0"
27
+ "@lushly-dev/afd-core": "2.2.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@convex-dev/auth": ">=0.0.95",
31
31
  "better-auth": "^1.6.22",
32
32
  "react": "^18.0.0 || ^19.0.0",
33
33
  "zod": "^4.0.0",
34
- "@lushly-dev/afd-server": "^2.1.0"
34
+ "@lushly-dev/afd-server": "^2.2.0"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@lushly-dev/afd-server": {
@@ -52,20 +52,21 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@auth/core": "^0.41.3",
55
- "better-auth": "^1.6.22",
56
55
  "@testing-library/react": "^16.3.3",
57
56
  "@types/react": "^19.3.0",
57
+ "@types/react-dom": "19.3.0",
58
58
  "@vitest/coverage-v8": "^4.1.11",
59
+ "better-auth": "^1.6.22",
60
+ "convex": "1.32.0",
61
+ "esbuild": "0.28.2",
59
62
  "jsdom": "^30.1.0",
63
+ "playwright": "1.64.0",
60
64
  "react": "^19.3.0",
61
65
  "react-dom": "^19.3.0",
62
66
  "typescript": "^5.9.3",
63
67
  "vitest": "^4.1.11",
64
- "zod": "^4.5.4",
65
- "esbuild": "0.28.2",
66
- "playwright": "1.64.0",
67
- "@types/react-dom": "19.3.0",
68
- "@lushly-dev/afd-server": "2.1.0"
68
+ "zod": "^4.6.6",
69
+ "@lushly-dev/afd-server": "2.2.0"
69
70
  },
70
71
  "keywords": [
71
72
  "afd",
@@ -99,6 +100,8 @@
99
100
  "test": "vitest run",
100
101
  "test:watch": "vitest",
101
102
  "clean": "rm -rf dist",
102
- "test:browser": "vitest run --config browser/vitest.config.ts"
103
+ "test:browser": "vitest run --config browser/vitest.config.ts",
104
+ "test:hosted:better-auth": "node hosted/better-auth/run.mjs --hosted",
105
+ "test:hosted:convex": "node hosted/convex/run.mjs --hosted"
103
106
  }
104
107
  }