@multiplatform.one/keycloak 7.90.5 → 8.0.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
@@ -27,8 +27,6 @@ Peers: `react`, `@multiplatform.one/logger`, `@multiplatform.one/platform`.
27
27
  - **Token helpers** — `isTokenExpired`, `validOrRefreshableToken`,
28
28
  `getKeycloakBearerToken`, wallet-claim readers (`getWalletsFromToken`,
29
29
  `getWalletAddresses`, `hasWallets`)
30
- - **`@multiplatform.one/keycloak/one`** — `createAuth()` Better Auth server
31
- factory (stateless JWE sessions, Keycloak OAuth, Expo support)
32
30
 
33
31
  ## What it must not do
34
32
 
@@ -365,6 +365,15 @@ function getAuthClient(baseURL) {
365
365
  return _client;
366
366
  }
367
367
 
368
+ // ../auth/src/accessTokenRefresh.ts
369
+ var FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
370
+ var ACCESS_TOKEN_RETRY_DELAYS_MS = [0, 400, 1e3];
371
+ function classifyAccessTokenResponse(status, body) {
372
+ if (status === 401 || status === 403) return "signed-out";
373
+ if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
374
+ return "transient";
375
+ }
376
+
368
377
  // src/betterAuth/accountSelection.ts
369
378
  var validationErrorCode = "VALIDATION_ERROR";
370
379
  var known;
@@ -399,13 +408,6 @@ var signedOutUntil = 0;
399
408
  var unavailableUntil = 0;
400
409
  var SIGNED_OUT_COOLDOWN_MS = 15e3;
401
410
  var TRANSIENT_COOLDOWN_MS = 3e3;
402
- var RETRY_DELAYS_MS = [0, 400, 1e3];
403
- var FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
404
- function classifyAccessTokenResponse(status, body) {
405
- if (status === 401 || status === 403) return "signed-out";
406
- if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
407
- return "transient";
408
- }
409
411
  function notifySessionInvalidated() {
410
412
  try {
411
413
  const client = getAuthClient();
@@ -458,8 +460,8 @@ async function fetchTokenOnce() {
458
460
  }
459
461
  async function fetchToken() {
460
462
  let last = "transient";
461
- for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
462
- const wait = RETRY_DELAYS_MS[i];
463
+ for (let i = 0; i < ACCESS_TOKEN_RETRY_DELAYS_MS.length; i++) {
464
+ const wait = ACCESS_TOKEN_RETRY_DELAYS_MS[i];
463
465
  if (wait) await sleep(wait);
464
466
  const result = await fetchTokenOnce();
465
467
  if (result.ok) {
@@ -311,6 +311,15 @@ function getAuthClient(baseURL) {
311
311
  return _client;
312
312
  }
313
313
 
314
+ // ../auth/src/accessTokenRefresh.ts
315
+ var FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
316
+ var ACCESS_TOKEN_RETRY_DELAYS_MS = [0, 400, 1e3];
317
+ function classifyAccessTokenResponse(status, body) {
318
+ if (status === 401 || status === 403) return "signed-out";
319
+ if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
320
+ return "transient";
321
+ }
322
+
314
323
  // src/betterAuth/accountSelection.ts
315
324
  var validationErrorCode = "VALIDATION_ERROR";
316
325
  var known;
@@ -345,13 +354,6 @@ var signedOutUntil = 0;
345
354
  var unavailableUntil = 0;
346
355
  var SIGNED_OUT_COOLDOWN_MS = 15e3;
347
356
  var TRANSIENT_COOLDOWN_MS = 3e3;
348
- var RETRY_DELAYS_MS = [0, 400, 1e3];
349
- var FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
350
- function classifyAccessTokenResponse(status, body) {
351
- if (status === 401 || status === 403) return "signed-out";
352
- if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
353
- return "transient";
354
- }
355
357
  function notifySessionInvalidated() {
356
358
  try {
357
359
  const client = getAuthClient();
@@ -404,8 +406,8 @@ async function fetchTokenOnce() {
404
406
  }
405
407
  async function fetchToken() {
406
408
  let last = "transient";
407
- for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
408
- const wait = RETRY_DELAYS_MS[i];
409
+ for (let i = 0; i < ACCESS_TOKEN_RETRY_DELAYS_MS.length; i++) {
410
+ const wait = ACCESS_TOKEN_RETRY_DELAYS_MS[i];
409
411
  if (wait) await sleep(wait);
410
412
  const result = await fetchTokenOnce();
411
413
  if (result.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/keycloak",
3
- "version": "7.90.5",
3
+ "version": "8.0.0",
4
4
  "description": "keycloak client for multiplatform.one ecosystem",
5
5
  "keywords": [
6
6
  "auth",
@@ -42,13 +42,6 @@
42
42
  "require": "./dist/cjs/index.cjs",
43
43
  "default": "./dist/esm/index.mjs"
44
44
  },
45
- "./one": {
46
- "source": "./src/one.ts",
47
- "types": "./types/one.d.ts",
48
- "import": "./dist/esm/one.mjs",
49
- "require": "./dist/cjs/one.cjs",
50
- "default": "./dist/esm/one.mjs"
51
- },
52
45
  "./oauth": {
53
46
  "source": "./src/oauth/index.ts",
54
47
  "types": "./types/oauth/index.d.ts",
@@ -62,16 +55,17 @@
62
55
  },
63
56
  "dependencies": {
64
57
  "@better-auth/expo": "~1.6.33",
65
- "@multiplatform.one/keycloak-js": "^7.90.5",
66
- "@multiplatform.one/store": "^7.90.5",
58
+ "@multiplatform.one/auth": "^8.0.0",
59
+ "@multiplatform.one/keycloak-js": "^8.0.0",
60
+ "@multiplatform.one/store": "^8.0.0",
67
61
  "better-auth": "~1.6.33",
68
62
  "jwt-decode": "^4.0.0"
69
63
  },
70
64
  "devDependencies": {
71
- "@multiplatform.one/config": "7.90.5",
72
- "@multiplatform.one/logger": "7.90.5",
73
- "@multiplatform.one/platform": "7.90.5",
74
- "@multiplatform.one/test-utils": "7.90.5",
65
+ "@multiplatform.one/config": "8.0.0",
66
+ "@multiplatform.one/logger": "8.0.0",
67
+ "@multiplatform.one/platform": "8.0.0",
68
+ "@multiplatform.one/test-utils": "8.0.0",
75
69
  "@tamagui/build": "2.7.6",
76
70
  "@testing-library/jest-dom": "^7.0.1",
77
71
  "@testing-library/react": "^16.3.3",
@@ -85,8 +79,8 @@
85
79
  "vitest": "^4.1.12"
86
80
  },
87
81
  "peerDependencies": {
88
- "@multiplatform.one/logger": "^7.90.5",
89
- "@multiplatform.one/platform": "^7.90.5",
82
+ "@multiplatform.one/logger": "^8.0.0",
83
+ "@multiplatform.one/platform": "^8.0.0",
90
84
  "react": "^19.1.0",
91
85
  "react-native": ">=0.83.0"
92
86
  },
@@ -8,15 +8,16 @@
8
8
  * token fetch then 400'd at validation, the Frappe layer fell back to guest
9
9
  * cookies, and a signed-in user was told to sign in.
10
10
  *
11
- * The last case drives the REAL Better Auth router that `createAuth` builds,
11
+ * The last case drives the REAL Better Auth router,
12
12
  * so the shape is checked against the installed version rather than against a
13
13
  * hand-copied schema. A 400 VALIDATION_ERROR fails; 401 is the pass, because
14
14
  * the request is unauthenticated by design — the point is that the body
15
15
  * reached the auth check at all.
16
16
  */
17
17
 
18
+ import { betterAuth } from "better-auth";
19
+ import { genericOAuth, keycloak } from "better-auth/plugins";
18
20
  import { beforeEach, describe, expect, it, vi } from "vitest";
19
- import { createAuth } from "../one";
20
21
  import {
21
22
  currentSelection,
22
23
  isBodyRefusal,
@@ -99,16 +100,27 @@ describe("requestAccessToken negotiation", () => {
99
100
  });
100
101
 
101
102
  describe("against the installed Better Auth router", () => {
102
- const auth = createAuth({
103
- baseUrl,
103
+ const auth = betterAuth({
104
+ basePath: "/api/auth",
105
+ baseURL: baseUrl,
104
106
  secret: "test-secret-not-a-real-one-0123456789",
105
- expoScheme: "test",
106
- keycloak: {
107
- baseUrl: "https://auth.example.test",
108
- realm: "test",
109
- clientId: "test-client",
110
- clientSecret: "",
107
+ trustedOrigins: [baseUrl],
108
+ advanced: { disableOriginCheck: false },
109
+ session: {
110
+ cookieCache: { enabled: true, maxAge: 60, strategy: "jwe", refreshCache: true },
111
111
  },
112
+ account: { storeStateStrategy: "cookie", storeAccountCookie: true },
113
+ plugins: [
114
+ genericOAuth({
115
+ config: [
116
+ keycloak({
117
+ clientId: "test-client",
118
+ clientSecret: "",
119
+ issuer: "https://auth.example.test/realms/test",
120
+ }),
121
+ ],
122
+ }),
123
+ ],
112
124
  });
113
125
 
114
126
  it("negotiates a body this server validates", async () => {
@@ -19,6 +19,11 @@
19
19
  * backoff, keep the session. A Keycloak restart must not look like logout.
20
20
  */
21
21
 
22
+ import {
23
+ ACCESS_TOKEN_RETRY_DELAYS_MS,
24
+ classifyAccessTokenResponse,
25
+ type AccessTokenFailureKind,
26
+ } from "@multiplatform.one/auth/access-token";
22
27
  import { requestAccessToken } from "./betterAuth/accountSelection";
23
28
  import { getAuthClient } from "./betterAuth/client";
24
29
 
@@ -32,16 +37,6 @@ let unavailableUntil = 0;
32
37
 
33
38
  const SIGNED_OUT_COOLDOWN_MS = 15_000;
34
39
  const TRANSIENT_COOLDOWN_MS = 3_000;
35
- const RETRY_DELAYS_MS = [0, 400, 1000] as const;
36
- const FAILED_TO_GET_ACCESS_TOKEN = "FAILED_TO_GET_ACCESS_TOKEN";
37
-
38
- type FailureKind = "signed-out" | "transient";
39
-
40
- function classifyAccessTokenResponse(status: number, body?: { code?: string } | null): FailureKind {
41
- if (status === 401 || status === 403) return "signed-out";
42
- if (status === 400 && body?.code === FAILED_TO_GET_ACCESS_TOKEN) return "signed-out";
43
- return "transient";
44
- }
45
40
 
46
41
  /**
47
42
  * A definitive refresh failure (Keycloak declared the refresh token dead —
@@ -90,7 +85,7 @@ export async function getKeycloakBearerToken(): Promise<string | undefined> {
90
85
  }
91
86
 
92
87
  async function fetchTokenOnce(): Promise<
93
- { ok: true; token: string; expiresAt: number } | { ok: false; kind: FailureKind }
88
+ { ok: true; token: string; expiresAt: number } | { ok: false; kind: AccessTokenFailureKind }
94
89
  > {
95
90
  try {
96
91
  let payload: unknown = null;
@@ -127,9 +122,9 @@ async function fetchTokenOnce(): Promise<
127
122
  }
128
123
 
129
124
  async function fetchToken(): Promise<string | undefined> {
130
- let last: FailureKind = "transient";
131
- for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
132
- const wait = RETRY_DELAYS_MS[i];
125
+ let last: AccessTokenFailureKind = "transient";
126
+ for (let i = 0; i < ACCESS_TOKEN_RETRY_DELAYS_MS.length; i++) {
127
+ const wait = ACCESS_TOKEN_RETRY_DELAYS_MS[i];
133
128
  if (wait) await sleep(wait);
134
129
  const result = await fetchTokenOnce();
135
130
  if (result.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"frappeToken.d.ts","sourceRoot":"","sources":["../src/frappeToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAiDH;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW1E;AAgED,wDAAwD;AACxD,wBAAgB,wBAAwB,SAKvC;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,IAAI,MAAM,IAAI,CAiB/D"}
1
+ {"version":3,"file":"frappeToken.d.ts","sourceRoot":"","sources":["../src/frappeToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA4CH;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW1E;AAgED,wDAAwD;AACxD,wBAAgB,wBAAwB,SAKvC;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,IAAI,MAAM,IAAI,CAiB/D"}
package/dist/cjs/one.cjs DELETED
@@ -1,82 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/one.ts
21
- var one_exports = {};
22
- __export(one_exports, {
23
- createAuth: () => createAuth
24
- });
25
- module.exports = __toCommonJS(one_exports);
26
- var import_better_auth = require("better-auth");
27
- var import_plugins = require("better-auth/plugins");
28
- var import_expo = require("@better-auth/expo");
29
- var _auth;
30
- function createAuth(config) {
31
- if (_auth) return _auth;
32
- const baseUrl = config.baseUrl || (typeof process !== "undefined" ? process.env?.BASE_URL || `http://localhost:${process.env?.ONE_PORT || "3000"}` : "http://localhost:3000");
33
- const keycloakBaseUrl = config.keycloak?.baseUrl || `${baseUrl}/auth`;
34
- const realm = config.keycloak?.realm || "master";
35
- const issuer = `${keycloakBaseUrl}/realms/${realm}`;
36
- const envTrustedOrigins = (typeof process !== "undefined" && process.env?.KEYCLOAK_TRUSTED_ORIGINS || "").split(",").map((origin) => origin.trim()).filter(Boolean);
37
- _auth = (0, import_better_auth.betterAuth)({
38
- basePath: "/api/auth",
39
- baseURL: baseUrl,
40
- secret: config.secret || "",
41
- trustedOrigins: [
42
- .../* @__PURE__ */ new Set([
43
- baseUrl,
44
- keycloakBaseUrl,
45
- ...config.expoScheme ? [`${config.expoScheme}://`] : [],
46
- ...envTrustedOrigins,
47
- ...config.trustedOrigins || []
48
- ])
49
- ],
50
- advanced: {
51
- // better-auth skips origin checks entirely when NODE_ENV=test; pin the
52
- // check on so the trustedOrigins contract is enforced (and testable)
53
- // regardless of environment.
54
- disableOriginCheck: false
55
- },
56
- session: {
57
- cookieCache: {
58
- enabled: true,
59
- maxAge: 7 * 24 * 60 * 60,
60
- strategy: "jwe",
61
- refreshCache: true
62
- }
63
- },
64
- account: {
65
- storeStateStrategy: "cookie",
66
- storeAccountCookie: true
67
- },
68
- plugins: [
69
- (0, import_expo.expo)(),
70
- (0, import_plugins.genericOAuth)({
71
- config: [
72
- (0, import_plugins.keycloak)({
73
- clientId: config.keycloak.clientId || "",
74
- clientSecret: config.keycloak.clientSecret || "",
75
- issuer
76
- })
77
- ]
78
- })
79
- ]
80
- });
81
- return _auth;
82
- }
package/dist/esm/one.mjs DELETED
@@ -1,61 +0,0 @@
1
- // src/one.ts
2
- import { betterAuth } from "better-auth";
3
- import { genericOAuth, keycloak } from "better-auth/plugins";
4
- import { expo } from "@better-auth/expo";
5
- var _auth;
6
- function createAuth(config) {
7
- if (_auth) return _auth;
8
- const baseUrl = config.baseUrl || (typeof process !== "undefined" ? process.env?.BASE_URL || `http://localhost:${process.env?.ONE_PORT || "3000"}` : "http://localhost:3000");
9
- const keycloakBaseUrl = config.keycloak?.baseUrl || `${baseUrl}/auth`;
10
- const realm = config.keycloak?.realm || "master";
11
- const issuer = `${keycloakBaseUrl}/realms/${realm}`;
12
- const envTrustedOrigins = (typeof process !== "undefined" && process.env?.KEYCLOAK_TRUSTED_ORIGINS || "").split(",").map((origin) => origin.trim()).filter(Boolean);
13
- _auth = betterAuth({
14
- basePath: "/api/auth",
15
- baseURL: baseUrl,
16
- secret: config.secret || "",
17
- trustedOrigins: [
18
- .../* @__PURE__ */ new Set([
19
- baseUrl,
20
- keycloakBaseUrl,
21
- ...config.expoScheme ? [`${config.expoScheme}://`] : [],
22
- ...envTrustedOrigins,
23
- ...config.trustedOrigins || []
24
- ])
25
- ],
26
- advanced: {
27
- // better-auth skips origin checks entirely when NODE_ENV=test; pin the
28
- // check on so the trustedOrigins contract is enforced (and testable)
29
- // regardless of environment.
30
- disableOriginCheck: false
31
- },
32
- session: {
33
- cookieCache: {
34
- enabled: true,
35
- maxAge: 7 * 24 * 60 * 60,
36
- strategy: "jwe",
37
- refreshCache: true
38
- }
39
- },
40
- account: {
41
- storeStateStrategy: "cookie",
42
- storeAccountCookie: true
43
- },
44
- plugins: [
45
- expo(),
46
- genericOAuth({
47
- config: [
48
- keycloak({
49
- clientId: config.keycloak.clientId || "",
50
- clientSecret: config.keycloak.clientSecret || "",
51
- issuer
52
- })
53
- ]
54
- })
55
- ]
56
- });
57
- return _auth;
58
- }
59
- export {
60
- createAuth
61
- };
@@ -1,107 +0,0 @@
1
- // @vitest-environment jsdom
2
- // jsdom, not the package's happy-dom: happy-dom's Request and Response drop
3
- // Cookie and Set-Cookie (forbidden headers in a browser), and the cookies are
4
- // the point here. jsdom leaves Node's own fetch classes in place.
5
- /**
6
- * FZ-8696: native sign-in through createAuth must come back signed in.
7
- *
8
- * createAuth wires Keycloak through Better Auth's genericOAuth, whose callback
9
- * is /oauth2/callback/keycloak. @better-auth/expo hands the session to the
10
- * app by appending ?cookie=<set-cookie> to the app-scheme redirect, but only
11
- * on the callback paths its after-hook matches. Under better-auth 1.6.30 that
12
- * was /callback* alone, so the genericOAuth callback came back as a bare
13
- * <scheme>:/// and the app stayed signed out with no error (Lookout FZ-8618).
14
- *
15
- * This drives the real sign-in and callback against a stubbed Keycloak, under
16
- * whatever better-auth version MPO pins, and checks the session rides the
17
- * redirect.
18
- */
19
-
20
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
- import { createAuth } from "./one";
22
-
23
- const appUrl = "http://app.test";
24
- const keycloakUrl = "http://kc.test/auth";
25
- const realm = "mpo";
26
- const issuer = `${keycloakUrl}/realms/${realm}`;
27
- const scheme = "mpo-test";
28
-
29
- function stubKeycloak() {
30
- const realFetch = globalThis.fetch;
31
- vi.stubGlobal(
32
- "fetch",
33
- vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
34
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
35
- const json = (body: unknown) =>
36
- new Response(JSON.stringify(body), { headers: { "content-type": "application/json" } });
37
- if (url === `${issuer}/.well-known/openid-configuration`) {
38
- return json({
39
- issuer,
40
- authorization_endpoint: `${issuer}/protocol/openid-connect/auth`,
41
- token_endpoint: `${issuer}/protocol/openid-connect/token`,
42
- userinfo_endpoint: `${issuer}/protocol/openid-connect/userinfo`,
43
- });
44
- }
45
- if (url === `${issuer}/protocol/openid-connect/token`) {
46
- return json({ access_token: "access", token_type: "Bearer", expires_in: 300 });
47
- }
48
- if (url === `${issuer}/protocol/openid-connect/userinfo`) {
49
- return json({
50
- sub: "user-1",
51
- email: "ada@example.test",
52
- name: "Ada",
53
- email_verified: true,
54
- });
55
- }
56
- return realFetch(input, init);
57
- }),
58
- );
59
- }
60
-
61
- beforeEach(stubKeycloak);
62
- afterEach(() => {
63
- vi.unstubAllGlobals();
64
- });
65
-
66
- describe("createAuth native sign-in (FZ-8696)", () => {
67
- it("hands the session back on the genericOAuth callback's app-scheme redirect", async () => {
68
- const auth = createAuth({
69
- baseUrl: appUrl,
70
- secret: "a-test-secret-that-is-long-enough-32",
71
- expoScheme: scheme,
72
- keycloak: { baseUrl: keycloakUrl, realm, clientId: "app", clientSecret: "secret" },
73
- });
74
-
75
- const signIn = await auth.handler(
76
- new Request(`${appUrl}/api/auth/sign-in/oauth2`, {
77
- method: "POST",
78
- headers: { "content-type": "application/json", "expo-origin": `${scheme}://` },
79
- body: JSON.stringify({
80
- providerId: "keycloak",
81
- callbackURL: `${scheme}://`,
82
- disableRedirect: true,
83
- }),
84
- }),
85
- );
86
- expect(signIn.status).toBe(200);
87
- const { url } = (await signIn.json()) as { url: string };
88
- const state = new URL(url).searchParams.get("state");
89
- expect(state).toBeTruthy();
90
- const cookies = signIn.headers
91
- .getSetCookie()
92
- .map((cookie) => cookie.split(";")[0])
93
- .join("; ");
94
- expect(cookies).toMatch(/oauth_state=/);
95
-
96
- const callback = await auth.handler(
97
- new Request(`${appUrl}/api/auth/oauth2/callback/keycloak?code=abc&state=${state}`, {
98
- headers: { cookie: cookies },
99
- }),
100
- );
101
- expect(callback.status).toBe(302);
102
- const location = new URL(callback.headers.get("location") ?? "");
103
- expect(location.protocol).toBe(`${scheme}:`);
104
- const handedBack = location.searchParams.get("cookie") ?? "";
105
- expect(handedBack, `redirect was ${location.href}`).toMatch(/better-auth\.session_token=/);
106
- });
107
- });
package/src/one.ts DELETED
@@ -1,111 +0,0 @@
1
- /**
2
- * File: /src/one.ts
3
- * Project: @multiplatform.one/keycloak
4
- *
5
- * Better Auth server configuration factory for apps using One.
6
- * Replaces the previous Auth.js (createAuthConfig) approach.
7
- * Stateless sessions — no database required.
8
- */
9
-
10
- import { betterAuth } from "better-auth";
11
- import { genericOAuth, keycloak } from "better-auth/plugins";
12
- import { expo } from "@better-auth/expo";
13
-
14
- let _auth: ReturnType<typeof betterAuth<any>> | undefined;
15
-
16
- export function createAuth(config: AuthHandlerConfig) {
17
- if (_auth) return _auth;
18
- const baseUrl =
19
- config.baseUrl ||
20
- (typeof process !== "undefined"
21
- ? process.env?.BASE_URL || `http://localhost:${process.env?.ONE_PORT || "3000"}`
22
- : "http://localhost:3000");
23
- const keycloakBaseUrl = config.keycloak?.baseUrl || `${baseUrl}/auth`;
24
- const realm = config.keycloak?.realm || "master";
25
- const issuer = `${keycloakBaseUrl}/realms/${realm}`;
26
- // Extra origins (e.g. LAN devices hitting the server by IP) come from the
27
- // KEYCLOAK_TRUSTED_ORIGINS env var (comma-separated) and/or the
28
- // trustedOrigins option; both extend the always-trusted defaults.
29
- const envTrustedOrigins = (
30
- (typeof process !== "undefined" && process.env?.KEYCLOAK_TRUSTED_ORIGINS) ||
31
- ""
32
- )
33
- .split(",")
34
- .map((origin) => origin.trim())
35
- .filter(Boolean);
36
-
37
- _auth = betterAuth({
38
- basePath: "/api/auth",
39
- baseURL: baseUrl,
40
- secret: config.secret || "",
41
- trustedOrigins: [
42
- ...new Set([
43
- baseUrl,
44
- keycloakBaseUrl,
45
- ...(config.expoScheme ? [`${config.expoScheme}://`] : []),
46
- ...envTrustedOrigins,
47
- ...(config.trustedOrigins || []),
48
- ]),
49
- ],
50
- advanced: {
51
- // better-auth skips origin checks entirely when NODE_ENV=test; pin the
52
- // check on so the trustedOrigins contract is enforced (and testable)
53
- // regardless of environment.
54
- disableOriginCheck: false,
55
- },
56
- session: {
57
- cookieCache: {
58
- enabled: true,
59
- maxAge: 7 * 24 * 60 * 60,
60
- strategy: "jwe",
61
- refreshCache: true,
62
- },
63
- },
64
- account: {
65
- storeStateStrategy: "cookie",
66
- storeAccountCookie: true,
67
- },
68
- plugins: [
69
- expo(),
70
- genericOAuth({
71
- config: [
72
- keycloak({
73
- clientId: config.keycloak.clientId || "",
74
- clientSecret: config.keycloak.clientSecret || "",
75
- issuer,
76
- }),
77
- ],
78
- }),
79
- ],
80
- });
81
-
82
- return _auth;
83
- }
84
-
85
- export interface KeycloakOptions {
86
- adminPassword?: string;
87
- adminUsername?: string;
88
- baseUrl?: string;
89
- clientId: string;
90
- clientSecret: string;
91
- realm?: string;
92
- authorization?: Record<string, any>;
93
- }
94
-
95
- export interface AuthHandlerConfig {
96
- baseUrl?: string;
97
- keycloak: KeycloakOptions;
98
- secret?: string;
99
- scopes?: string[];
100
- /** Expo deep-link scheme (e.g. "multiplatform-one") — added to trustedOrigins. */
101
- expoScheme?: string;
102
- /**
103
- * Additional origins allowed to hit the auth route (e.g. LAN device
104
- * origins). Extends — never replaces — the defaults (baseUrl, keycloak
105
- * URL, expo scheme). Also readable from the KEYCLOAK_TRUSTED_ORIGINS env
106
- * var as a comma-separated list.
107
- */
108
- trustedOrigins?: string[];
109
- }
110
-
111
- export type Auth = ReturnType<typeof createAuth>;
package/types/one.d.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * File: /src/one.ts
3
- * Project: @multiplatform.one/keycloak
4
- *
5
- * Better Auth server configuration factory for apps using One.
6
- * Replaces the previous Auth.js (createAuthConfig) approach.
7
- * Stateless sessions — no database required.
8
- */
9
- export declare function createAuth(config: AuthHandlerConfig): import("better-auth").Auth<any>;
10
- export interface KeycloakOptions {
11
- adminPassword?: string;
12
- adminUsername?: string;
13
- baseUrl?: string;
14
- clientId: string;
15
- clientSecret: string;
16
- realm?: string;
17
- authorization?: Record<string, any>;
18
- }
19
- export interface AuthHandlerConfig {
20
- baseUrl?: string;
21
- keycloak: KeycloakOptions;
22
- secret?: string;
23
- scopes?: string[];
24
- /** Expo deep-link scheme (e.g. "multiplatform-one") — added to trustedOrigins. */
25
- expoScheme?: string;
26
- /**
27
- * Additional origins allowed to hit the auth route (e.g. LAN device
28
- * origins). Extends — never replaces — the defaults (baseUrl, keycloak
29
- * URL, expo scheme). Also readable from the KEYCLOAK_TRUSTED_ORIGINS env
30
- * var as a comma-separated list.
31
- */
32
- trustedOrigins?: string[];
33
- }
34
- export type Auth = ReturnType<typeof createAuth>;
35
- //# sourceMappingURL=one.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"one.d.ts","sourceRoot":"","sources":["../src/one.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,mCAmEnD;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC"}