@multiplatform.one/auth 7.17.0 → 7.19.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/auth",
3
- "version": "7.17.0",
3
+ "version": "7.19.0",
4
4
  "description": "Better Auth helpers for multiplatform.one apps with Keycloak",
5
5
  "keywords": [
6
6
  "auth",
@@ -48,8 +48,8 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@better-auth/expo": "^1.6.9",
51
- "@multiplatform.one/config": "7.17.0",
52
- "@multiplatform.one/test-utils": "7.17.0",
51
+ "@multiplatform.one/config": "7.19.0",
52
+ "@multiplatform.one/test-utils": "7.19.0",
53
53
  "@tamagui/build": "2.7.6",
54
54
  "@vitest/coverage-v8": "^4.1.5",
55
55
  "better-auth": "^1.6.9",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@better-auth/expo": "^1.4.0",
61
- "@multiplatform.one/wallet": "^7.17.0",
61
+ "@multiplatform.one/wallet": "^7.19.0",
62
62
  "better-auth": "^1.4.0"
63
63
  },
64
64
  "peerDependenciesMeta": {
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  APIError,
22
22
  createAuthEndpoint,
23
23
  createAuthMiddleware,
24
+ getAccessToken,
24
25
  getSessionFromCtx,
25
26
  isAPIError,
26
27
  } from "better-auth/api";
@@ -51,13 +52,14 @@ export type { AccessTokenFailureKind, RetryAttemptResult } from "./accessTokenRe
51
52
  const requestOriginStorage = new AsyncLocalStorage<string>();
52
53
 
53
54
  /**
54
- * The visitor origin for the request being handled. The app may sit behind
55
+ * The visitor origin for a request from a private-network dev host, else
56
+ * undefined so callers use the configured BASE_URL. The app may sit behind
55
57
  * the bench's dev proxy (browser → :8000 → one server :3456), where
56
58
  * `req.url` shows the proxy target — so prefer `X-Forwarded-Proto/Host`
57
59
  * (the bench proxy sets them for exactly this purpose), then the browser's
58
- * `Origin` header, then the request URL. Forwarded/origin hosts are only
59
- * honored when they match the private-network dev allowlist, so a spoofed
60
- * header can never steer OAuth redirects at a public host.
60
+ * `Origin` header, then the request URL. A public host never picks the
61
+ * origin: behind a TLS edge the request URL says http, and its Host is
62
+ * whatever the client sent.
61
63
  */
62
64
  function deriveRequestOrigin(req: Request): string | undefined {
63
65
  const isDevHost = (host: string) =>
@@ -69,21 +71,16 @@ function deriveRequestOrigin(req: Request): string | undefined {
69
71
  return `${proto}://${forwardedHost}`;
70
72
  }
71
73
 
72
- const originHeader = req.headers.get("origin");
73
- if (originHeader) {
74
+ for (const candidate of [req.headers.get("origin"), req.url]) {
75
+ if (!candidate) continue;
74
76
  try {
75
- const url = new URL(originHeader);
77
+ const url = new URL(candidate);
76
78
  if (isDevHost(url.host)) return url.origin;
77
79
  } catch {
78
- // fall through to the request URL
80
+ // not a URL; try the next candidate
79
81
  }
80
82
  }
81
-
82
- try {
83
- return new URL(req.url).origin;
84
- } catch {
85
- return undefined;
86
- }
83
+ return undefined;
87
84
  }
88
85
 
89
86
  // ─── Types ──────────────────────────────────────────────────────────
@@ -160,12 +157,21 @@ interface KeycloakSsoPluginOptions {
160
157
  clientSecret: string;
161
158
  /** Backchannel issuer (server-to-server), e.g. http://localhost:8080/auth/realms/myrealm */
162
159
  issuer: string;
163
- /** Fallback visitor origin when none can be derived from the request. */
160
+ /** Public app origin, used for every visitor that is not on a dev host. */
164
161
  baseUrl: string;
165
162
  /** Browser-facing Keycloak base for a visitor origin (proxy-aware). */
166
163
  publicKeycloakBaseFor: (requestOrigin?: string) => string | undefined;
167
164
  }
168
165
 
166
+ const accessTokenBodySchema = getAccessToken.options.body as
167
+ | { safeParse?: (value: unknown) => { success: boolean } }
168
+ | undefined;
169
+
170
+ /** Whether the installed Better Auth validates this `/get-access-token` body. */
171
+ function acceptsAccessTokenBody(body: unknown): boolean {
172
+ return accessTokenBodySchema?.safeParse?.(body).success !== false;
173
+ }
174
+
169
175
  /** Minimal shape of a stored OAuth account used by the SSO plugin. */
170
176
  interface KeycloakAccountLike {
171
177
  providerId?: string;
@@ -317,10 +323,17 @@ function createKeycloakSsoPlugin(options: KeycloakSsoPluginOptions) {
317
323
  // so it is a 200 with no token, as /get-session answers, instead
318
324
  // of a 4xx the browser logs. Runs before body validation, so the
319
325
  // client's body-shape negotiation never 400s for a visitor either.
326
+ // A signed-in >=1.7 body (`useAccountCookie`) on a server whose
327
+ // schema still demands `providerId` gets the Keycloak provider
328
+ // added, so the negotiation never 400s once signed in either.
320
329
  matcher: (ctx) => ctx.path === "/get-access-token",
321
330
  handler: createAuthMiddleware(async (ctx) => {
322
331
  const session = await getSessionFromCtx(ctx).catch(() => null);
323
332
  if (!session) return ctx.json(null);
333
+ const body = ctx.body as { useAccountCookie?: unknown } | undefined;
334
+ if (body?.useAccountCookie !== true || acceptsAccessTokenBody(body)) return;
335
+ const legacy = { ...body, providerId: "keycloak" };
336
+ if (acceptsAccessTokenBody(legacy)) return { context: { body: legacy } };
324
337
  }),
325
338
  },
326
339
  ],
@@ -649,8 +662,8 @@ export type AuthInstance = ReturnType<typeof createAuth>;
649
662
  * ```
650
663
  */
651
664
  export function createAuthRouteHandler(auth: AuthLike) {
652
- // Every auth request runs inside an AsyncLocalStorage scope carrying its
653
- // visitor origin (proxy-aware; see deriveRequestOrigin), so the dev
665
+ // An auth request from a dev host runs inside an AsyncLocalStorage scope
666
+ // carrying its visitor origin (proxy-aware; see deriveRequestOrigin), so the dev
654
667
  // redirect-URI/authorization-URL getters (createAuth) can follow the host
655
668
  // the visitor is actually on (survives the awaits inside better-auth).
656
669
  const handler = (req: Request) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AAc7D,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAmDvF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtD;;;OAGG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;CACzD;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;IAC5D,GAAG,EAAE;QACH,UAAU,EAAE,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAC9D,CAAC;CACH;AA4RD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB;;;;;;;;;;;;;;;;;;;GAmNzD;AAED,yCAAyC;AACzC,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAIzD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ;wBAK7B,OAAO;yBAAP,OAAO;EAK9B;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ;sBAOlB,OAAO;sBALP,OAAO;EAcvC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AAe7D,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AA+CvF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtD;;;OAGG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;CACzD;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;IAC5D,GAAG,EAAE;QACH,UAAU,EAAE,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAC9D,CAAC;CACH;AA4SD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB;;;;;;;;;;;;;;;;;;;GAmNzD;AAED,yCAAyC;AACzC,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAIzD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ;wBAK7B,OAAO;yBAAP,OAAO;EAK9B;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ;sBAOlB,OAAO;sBALP,OAAO;EAcvC"}