@godxjp/server-sdk 0.5.0 → 0.6.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
@@ -84,7 +84,7 @@ back to `defaultOrganizationContextId` for local dev). A session is bound to the
84
84
 
85
85
  | Member | Type | Behaviour |
86
86
  |---|---|---|
87
- | `routes` | `Hono` | `GET /login` (PKCE + state in a sealed 5-min `gx_txn` cookie, forwards `organization_context_id`), `GET /callback` (code exchange → `verifyAccessToken` → one `userinfo` call for display name → seals `{principal, accessToken, refreshToken?}` into `gx_sess`), `POST /refresh` (renews the session with the refresh token: 204 + new cookie, or 401 and the cookie cleared), `GET /logout` (best-effort revoke of the access AND refresh token, then redirect to `end_session_endpoint`). Every failure redirects to `/?sso_error=…&sso_error_description=…`. |
87
+ | `routes` | `Hono` | `GET /login` (PKCE + state in a sealed 5-min `gx_txn` cookie, forwards `organization_context_id`, keeps `return_to` when it is a same-origin path — see `safeReturnTo`), `GET /callback` (code exchange → `verifyAccessToken` → one `userinfo` call for display name → seals `{principal, accessToken, refreshToken?}` into `gx_sess`, then redirects to `return_to` or `/`), `POST /refresh` (renews the session with the refresh token: 204 + new cookie, or 401 and the cookie cleared), `GET /logout` (best-effort revoke of the access AND refresh token, then redirect to `end_session_endpoint`). Every failure redirects to `/?sso_error=…&sso_error_description=…`. |
88
88
  | `requireAuth` | middleware | Opens `gx_sess`; 401 `{"error":"unauthenticated","refreshable":bool}` when missing/expired — `refreshable: true` means `POST /refresh` can renew it without a new sign-in. Sets `c.set("principal", …)` and `c.set("accessToken", …)` (the `GodxVariables` pair). |
89
89
  | `requireWritable` | middleware | Enforces `organization_access_mode`: non-GET/HEAD/OPTIONS from a `read_only` org answers 403. |
90
90
  | `dockProxy` | middleware | Forwards `/api/dock/v1/*` to the platform with the session's Bearer token — the GoDX Dock `apiBase` lane: no CORS, no token in the page. Mount it behind `requireAuth` and **before** any `requireWritable` (Dock actions are the platform's to authorize). Passes `X-Request-Id` back through. |
@@ -107,6 +107,11 @@ async function api(path: string, init?: RequestInit): Promise<Response> {
107
107
  }
108
108
  ```
109
109
 
110
+ A 401 that is not (or no longer) refreshable means the session is over: send the browser to
111
+ `/auth/login?return_to=${encodeURIComponent(location.pathname + location.search)}` — once, however
112
+ many requests failed. The platform returns immediately while its own session is alive, and the
113
+ callback lands back on that page.
114
+
110
115
  ## API reference
111
116
 
112
117
  Every export per subpath, checked against `src/*.ts`.
package/dist/hono.d.ts CHANGED
@@ -28,6 +28,12 @@ interface GodxSsoConfig {
28
28
  */
29
29
  defaultOrganizationContextId?: string;
30
30
  }
31
+ /**
32
+ * `return_to` for `/login`: only a same-origin path ("/mail?x=1") is kept — anything that a
33
+ * browser could resolve to another origin ("//evil", "/\\evil", "https://…", control
34
+ * characters) is dropped, so the login flow can never become an open redirect.
35
+ */
36
+ declare function safeReturnTo(raw: string | undefined | null): string | undefined;
31
37
  type GodxVariables = {
32
38
  principal: Principal;
33
39
  accessToken: string;
@@ -41,4 +47,4 @@ declare function godxSso<E extends Env & {
41
47
  dockProxy: MiddlewareHandler<E>;
42
48
  };
43
49
 
44
- export { type GodxSsoConfig, type GodxVariables, godxSso };
50
+ export { type GodxSsoConfig, type GodxVariables, godxSso, safeReturnTo };
package/dist/hono.js CHANGED
@@ -16,6 +16,14 @@ import { deleteCookie, getCookie, setCookie } from "hono/cookie";
16
16
  var DEFAULT_SCOPE = "openid profile email offline_access";
17
17
  var DEFAULT_SESSION_MAX_AGE = 30 * 24 * 3600;
18
18
  var TXN_COOKIE = "gx_txn";
19
+ function safeReturnTo(raw) {
20
+ if (!raw || raw.length > 2048 || !/^\/(?![/\\])/.test(raw) || /[\u0000-\u001f\u007f\\]/.test(raw)) {
21
+ return void 0;
22
+ }
23
+ const base = "https://return-to.invalid";
24
+ const url = new URL(raw, base);
25
+ return url.origin === base ? url.pathname + url.search + url.hash : void 0;
26
+ }
19
27
  var SESSION_COOKIE = "gx_sess";
20
28
  function godxSso(getConfig) {
21
29
  const client = (c) => new GodxSsoClient(getConfig(c.env));
@@ -39,7 +47,8 @@ function godxSso(getConfig) {
39
47
  const state = randomToken();
40
48
  const nonce = randomToken();
41
49
  const organizationContextId = c.req.query("organization_context_id") ?? config.defaultOrganizationContextId;
42
- setCookie(c, TXN_COOKIE, await sealData({ v: pkce.verifier, s: state }, config.sessionSecret, 300), {
50
+ const returnTo = safeReturnTo(c.req.query("return_to"));
51
+ setCookie(c, TXN_COOKIE, await sealData({ v: pkce.verifier, s: state, r: returnTo }, config.sessionSecret, 300), {
43
52
  httpOnly: true,
44
53
  secure: secure(c),
45
54
  sameSite: "Lax",
@@ -108,7 +117,7 @@ function godxSso(getConfig) {
108
117
  } catch {
109
118
  }
110
119
  await writeSession(c, { p: principal, at: tokens.access_token, rt: tokens.refresh_token });
111
- return c.redirect("/");
120
+ return c.redirect(safeReturnTo(txn.r) ?? "/");
112
121
  });
113
122
  routes.post("/refresh", async (c) => {
114
123
  const config = cfg(c);
@@ -182,6 +191,7 @@ function godxSso(getConfig) {
182
191
  return { routes, requireAuth, requireWritable, dockProxy };
183
192
  }
184
193
  export {
185
- godxSso
194
+ godxSso,
195
+ safeReturnTo
186
196
  };
187
197
  //# sourceMappingURL=hono.js.map
package/dist/hono.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hono.ts"],"sourcesContent":["/**\n * ./hono — thin adapter over ./auth + ./http for Hono apps on Workers.\n * Mounts the browser SSO routes and exposes the auth middlewares.\n * Everything platform-shaped stays in the core modules.\n */\nimport { Hono } from \"hono\";\nimport type { Context, Env as HonoEnv, MiddlewareHandler } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport { createPkce, openData, randomToken, sealData, verifyAccessToken } from \"./auth\";\nimport type { Principal } from \"./auth\";\nimport { GodxSsoClient, loadIssuer } from \"./http\";\n\nexport interface GodxSsoConfig {\n issuer: string;\n serviceSlug: string;\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n sessionSecret: string;\n /**\n * Requested scopes. Default `openid profile email offline_access`: `offline_access` gets a\n * refresh token, so a session outlives one access token (see `POST <mount>/refresh`).\n */\n scope?: string;\n /**\n * How long a session with a refresh token may live, in seconds (default 30 days = the\n * platform's refresh-token lifetime). The access token inside is still short-lived.\n */\n sessionMaxAge?: number;\n /** Token audience = your ServiceInstance slug (defaults to serviceSlug). */\n audience?: string;\n /**\n * console_organization_id to fall back on when the login was not started\n * from a platform launch URL (which carries organization_context_id).\n * Useful in local dev; leave unset in production multi-org setups.\n */\n defaultOrganizationContextId?: string;\n}\n\n/** p = verified principal, at = access token, rt = refresh token (offline_access). */\ntype SessionData = { p: Principal; at: string; rt?: string };\n\nconst DEFAULT_SCOPE = \"openid profile email offline_access\";\nconst DEFAULT_SESSION_MAX_AGE = 30 * 24 * 3600;\n\nconst TXN_COOKIE = \"gx_txn\";\nconst SESSION_COOKIE = \"gx_sess\";\n\nexport type GodxVariables = { principal: Principal; accessToken: string };\n\nexport function godxSso<E extends HonoEnv & { Variables: GodxVariables }>(\n getConfig: (env: E[\"Bindings\"]) => GodxSsoConfig,\n) {\n const client = (c: Context<E>) => new GodxSsoClient(getConfig(c.env));\n const cfg = (c: Context<E>) => getConfig(c.env);\n const secure = (c: Context<E>) => c.req.url.startsWith(\"https:\");\n\n /**\n * With a refresh token the cookie lives as long as the refresh token may (the access token\n * inside still expires on time — requireAuth checks it); without one, exactly as long as the\n * access token, as before.\n */\n const writeSession = async (c: Context<E>, data: SessionData) => {\n const config = cfg(c);\n const ttl = data.rt\n ? (config.sessionMaxAge ?? DEFAULT_SESSION_MAX_AGE)\n : Math.max(0, data.p.expiresAt - Math.floor(Date.now() / 1000));\n setCookie(c, SESSION_COOKIE, await sealData(data, config.sessionSecret, ttl), {\n httpOnly: true,\n secure: secure(c),\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: ttl,\n });\n };\n\n const routes = new Hono<E>();\n\n routes.get(\"/login\", async (c) => {\n const config = cfg(c);\n const pkce = await createPkce();\n const state = randomToken();\n const nonce = randomToken();\n // The platform launch URL carries organization_context_id; forward it, or\n // fall back to the configured default (single-org / local dev).\n const organizationContextId =\n c.req.query(\"organization_context_id\") ?? config.defaultOrganizationContextId;\n setCookie(c, TXN_COOKIE, await sealData({ v: pkce.verifier, s: state }, config.sessionSecret, 300), {\n httpOnly: true,\n secure: secure(c),\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: 300,\n });\n return c.redirect(\n await client(c).authorizeUrl({\n state,\n nonce,\n codeChallenge: pkce.challenge,\n scope: config.scope ?? DEFAULT_SCOPE,\n organizationContextId,\n }),\n );\n });\n\n // Every failure lands back on the app with a human-readable description —\n // the SPA renders it; a bare 400 helps nobody.\n const failRedirect = (c: Context<E>, error: string, description: string) =>\n c.redirect(\n `/?sso_error=${encodeURIComponent(error)}&sso_error_description=${encodeURIComponent(description)}`,\n );\n\n routes.get(\"/callback\", async (c) => {\n const config = cfg(c);\n\n // The platform denied the launch (access_denied etc.) and already wrote a\n // localized, human-readable explanation into error_description. Show it.\n const platformError = c.req.query(\"error\");\n if (platformError) {\n return failRedirect(\n c,\n platformError,\n c.req.query(\"error_description\") ?? `サインインが拒否されました(${platformError})。`,\n );\n }\n\n const code = c.req.query(\"code\");\n const state = c.req.query(\"state\");\n const txnCookie = getCookie(c, TXN_COOKIE);\n deleteCookie(c, TXN_COOKIE, { path: \"/\" });\n\n const txn = txnCookie ? await openData<{ v: string; s: string }>(txnCookie, config.sessionSecret) : null;\n if (!code || !state || !txn || txn.s !== state) {\n return failRedirect(\n c,\n \"transaction_invalid\",\n \"ログイン処理の有効期限が切れたか、途中で中断されました。もう一度サインインしてください。\",\n );\n }\n\n let tokens;\n try {\n tokens = await client(c).exchangeCode({ code, codeVerifier: txn.v });\n } catch (e) {\n return failRedirect(\n c,\n \"token_exchange_failed\",\n `認可コードの交換に失敗しました(認可コードは一度しか使えません)。もう一度サインインしてください。詳細: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n let verified;\n try {\n verified = await verifyAccessToken(tokens.access_token, config);\n } catch (e) {\n return failRedirect(\n c,\n \"token_verification_failed\",\n `トークンの検証に失敗しました。サービス設定(issuer / service_slug)を確認してください。詳細: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n // One userinfo call at login so the UI has a display name without further round-trips.\n let principal = verified;\n try {\n const info = await client(c).userinfo(tokens.access_token);\n principal = Object.freeze({ ...verified, name: info.name, email: info.email });\n } catch {\n // profile display data is optional; the verified principal stands on its own\n }\n\n await writeSession(c, { p: principal, at: tokens.access_token, rt: tokens.refresh_token });\n return c.redirect(\"/\");\n });\n\n /**\n * Renews an expired (or expiring) session with its refresh token. The platform ROTATES refresh\n * tokens and revokes the whole family when an old one is replayed, so this is a dedicated route\n * the browser calls ONCE (single-flight) when an API answers 401 `refreshable: true`, then\n * retries — refreshing inside `requireAuth` would let parallel requests replay the same token\n * and log the user out.\n *\n * 204 = renewed (new cookie set). 401 = no refresh token, or the platform refused it (expired,\n * revoked, membership gone): the session cookie is cleared and the app should sign in again.\n */\n routes.post(\"/refresh\", async (c) => {\n const config = cfg(c);\n const raw = getCookie(c, SESSION_COOKIE);\n const session = raw ? await openData<SessionData>(raw, config.sessionSecret) : null;\n if (!session?.rt) return c.json({ error: \"unauthenticated\", refreshable: false }, 401);\n try {\n const tokens = await client(c).refresh(session.rt);\n const verified = await verifyAccessToken(tokens.access_token, config);\n // Display data came from userinfo at login; the same person, so it carries over.\n const principal = Object.freeze({ ...verified, name: session.p.name, email: session.p.email });\n await writeSession(c, { p: principal, at: tokens.access_token, rt: tokens.refresh_token ?? session.rt });\n return c.body(null, 204);\n } catch (e) {\n // The platform's reason (invalid_grant, a revoked family, …) is the only clue an operator gets.\n console.warn(\"godx-sso: refresh failed\", e instanceof Error ? e.message : String(e));\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n return c.json({ error: \"unauthenticated\", refreshable: false }, 401);\n }\n });\n\n routes.get(\"/logout\", async (c) => {\n const config = cfg(c);\n const raw = getCookie(c, SESSION_COOKIE);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n if (raw) {\n const session = await openData<SessionData>(raw, config.sessionSecret);\n if (session) {\n await client(c).revoke(session.at).catch(() => {});\n // The refresh token outlives the access token by weeks: it must not survive a sign-out.\n if (session.rt) await client(c).revoke(session.rt).catch(() => {});\n }\n }\n const { metadata } = await loadIssuer(config.issuer);\n return c.redirect(metadata.end_session_endpoint ?? \"/\");\n });\n\n const requireAuth: MiddlewareHandler<E> = async (c, next) => {\n const raw = getCookie(c, SESSION_COOKIE);\n const session = raw ? await openData<SessionData>(raw, cfg(c).sessionSecret) : null;\n if (!session || session.p.expiresAt <= Math.floor(Date.now() / 1000)) {\n // `refreshable`: POST <mount>/refresh can renew it without a new sign-in.\n return c.json({ error: \"unauthenticated\", refreshable: !!session?.rt }, 401);\n }\n c.set(\"principal\", session.p);\n c.set(\"accessToken\", session.at);\n await next();\n };\n\n /**\n * Forwards /api/dock/v1/* to the platform with the session's Bearer token —\n * the GoDX Dock's `apiBase` lane: no CORS, no token in the page. Mount it\n * BEHIND requireAuth and BEFORE any requireWritable (Dock actions are the\n * platform's calls to authorize, not this app's writes).\n */\n const dockProxy: MiddlewareHandler<E> = async (c) => {\n const config = cfg(c);\n const url = new URL(c.req.url);\n const target = new URL(url.pathname + url.search, config.issuer);\n const method = c.req.method;\n const res = await fetch(target, {\n method,\n headers: {\n Authorization: `Bearer ${c.get(\"accessToken\")}`,\n Accept: \"application/json\",\n \"Content-Type\": c.req.header(\"Content-Type\") ?? \"application/json\",\n },\n body: method === \"GET\" || method === \"HEAD\" ? undefined : await c.req.arrayBuffer(),\n });\n const headers = new Headers({ \"Content-Type\": res.headers.get(\"Content-Type\") ?? \"application/json\" });\n const requestId = res.headers.get(\"X-Request-Id\");\n if (requestId) headers.set(\"X-Request-Id\", requestId);\n return new Response(res.body, { status: res.status, headers });\n };\n\n /** Enforces organization_access_mode: read-only orgs may not write. */\n const requireWritable: MiddlewareHandler<E> = async (c, next) => {\n const safe = c.req.method === \"GET\" || c.req.method === \"HEAD\" || c.req.method === \"OPTIONS\";\n if (!safe && c.get(\"principal\")?.accessMode !== \"full\") {\n return c.json({ error: \"organization is read-only\" }, 403);\n }\n await next();\n };\n\n return { routes, requireAuth, requireWritable, dockProxy };\n}\n"],"mappings":";;;;;;;;;;;;;AAKA,SAAS,YAAY;AAErB,SAAS,cAAc,WAAW,iBAAiB;AAmCnD,IAAM,gBAAgB;AACtB,IAAM,0BAA0B,KAAK,KAAK;AAE1C,IAAM,aAAa;AACnB,IAAM,iBAAiB;AAIhB,SAAS,QACd,WACA;AACA,QAAM,SAAS,CAAC,MAAkB,IAAI,cAAc,UAAU,EAAE,GAAG,CAAC;AACpE,QAAM,MAAM,CAAC,MAAkB,UAAU,EAAE,GAAG;AAC9C,QAAM,SAAS,CAAC,MAAkB,EAAE,IAAI,IAAI,WAAW,QAAQ;AAO/D,QAAM,eAAe,OAAO,GAAe,SAAsB;AAC/D,UAAM,SAAS,IAAI,CAAC;AACpB,UAAM,MAAM,KAAK,KACZ,OAAO,iBAAiB,0BACzB,KAAK,IAAI,GAAG,KAAK,EAAE,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AAChE,cAAU,GAAG,gBAAgB,MAAM,SAAS,MAAM,OAAO,eAAe,GAAG,GAAG;AAAA,MAC5E,UAAU;AAAA,MACV,QAAQ,OAAO,CAAC;AAAA,MAChB,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,IAAI,KAAQ;AAE3B,SAAO,IAAI,UAAU,OAAO,MAAM;AAChC,UAAM,SAAS,IAAI,CAAC;AACpB,UAAM,OAAO,MAAM,WAAW;AAC9B,UAAM,QAAQ,YAAY;AAC1B,UAAM,QAAQ,YAAY;AAG1B,UAAM,wBACJ,EAAE,IAAI,MAAM,yBAAyB,KAAK,OAAO;AACnD,cAAU,GAAG,YAAY,MAAM,SAAS,EAAE,GAAG,KAAK,UAAU,GAAG,MAAM,GAAG,OAAO,eAAe,GAAG,GAAG;AAAA,MAClG,UAAU;AAAA,MACV,QAAQ,OAAO,CAAC;AAAA,MAChB,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,EAAE;AAAA,MACP,MAAM,OAAO,CAAC,EAAE,aAAa;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,eAAe,KAAK;AAAA,QACpB,OAAO,OAAO,SAAS;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAID,QAAM,eAAe,CAAC,GAAe,OAAe,gBAClD,EAAE;AAAA,IACA,eAAe,mBAAmB,KAAK,CAAC,0BAA0B,mBAAmB,WAAW,CAAC;AAAA,EACnG;AAEF,SAAO,IAAI,aAAa,OAAO,MAAM;AACnC,UAAM,SAAS,IAAI,CAAC;AAIpB,UAAM,gBAAgB,EAAE,IAAI,MAAM,OAAO;AACzC,QAAI,eAAe;AACjB,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,EAAE,IAAI,MAAM,mBAAmB,KAAK,uFAAiB,aAAa;AAAA,MACpE;AAAA,IACF;AAEA,UAAM,OAAO,EAAE,IAAI,MAAM,MAAM;AAC/B,UAAM,QAAQ,EAAE,IAAI,MAAM,OAAO;AACjC,UAAM,YAAY,UAAU,GAAG,UAAU;AACzC,iBAAa,GAAG,YAAY,EAAE,MAAM,IAAI,CAAC;AAEzC,UAAM,MAAM,YAAY,MAAM,SAAmC,WAAW,OAAO,aAAa,IAAI;AACpG,QAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,IAAI,MAAM,OAAO;AAC9C,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,OAAO,CAAC,EAAE,aAAa,EAAE,MAAM,cAAc,IAAI,EAAE,CAAC;AAAA,IACrE,SAAS,GAAG;AACV,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,uTAAwD,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACpG;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,kBAAkB,OAAO,cAAc,MAAM;AAAA,IAChE,SAAS,GAAG;AACV,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,4OAA6D,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACzG;AAAA,IACF;AAGA,QAAI,YAAY;AAChB,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,CAAC,EAAE,SAAS,OAAO,YAAY;AACzD,kBAAY,OAAO,OAAO,EAAE,GAAG,UAAU,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,IAC/E,QAAQ;AAAA,IAER;AAEA,UAAM,aAAa,GAAG,EAAE,GAAG,WAAW,IAAI,OAAO,cAAc,IAAI,OAAO,cAAc,CAAC;AACzF,WAAO,EAAE,SAAS,GAAG;AAAA,EACvB,CAAC;AAYD,SAAO,KAAK,YAAY,OAAO,MAAM;AACnC,UAAM,SAAS,IAAI,CAAC;AACpB,UAAM,MAAM,UAAU,GAAG,cAAc;AACvC,UAAM,UAAU,MAAM,MAAM,SAAsB,KAAK,OAAO,aAAa,IAAI;AAC/E,QAAI,CAAC,SAAS,GAAI,QAAO,EAAE,KAAK,EAAE,OAAO,mBAAmB,aAAa,MAAM,GAAG,GAAG;AACrF,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,CAAC,EAAE,QAAQ,QAAQ,EAAE;AACjD,YAAM,WAAW,MAAM,kBAAkB,OAAO,cAAc,MAAM;AAEpE,YAAM,YAAY,OAAO,OAAO,EAAE,GAAG,UAAU,MAAM,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE,MAAM,CAAC;AAC7F,YAAM,aAAa,GAAG,EAAE,GAAG,WAAW,IAAI,OAAO,cAAc,IAAI,OAAO,iBAAiB,QAAQ,GAAG,CAAC;AACvG,aAAO,EAAE,KAAK,MAAM,GAAG;AAAA,IACzB,SAAS,GAAG;AAEV,cAAQ,KAAK,4BAA4B,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AACnF,mBAAa,GAAG,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAC7C,aAAO,EAAE,KAAK,EAAE,OAAO,mBAAmB,aAAa,MAAM,GAAG,GAAG;AAAA,IACrE;AAAA,EACF,CAAC;AAED,SAAO,IAAI,WAAW,OAAO,MAAM;AACjC,UAAM,SAAS,IAAI,CAAC;AACpB,UAAM,MAAM,UAAU,GAAG,cAAc;AACvC,iBAAa,GAAG,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAC7C,QAAI,KAAK;AACP,YAAM,UAAU,MAAM,SAAsB,KAAK,OAAO,aAAa;AACrE,UAAI,SAAS;AACX,cAAM,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAEjD,YAAI,QAAQ,GAAI,OAAM,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACnE;AAAA,IACF;AACA,UAAM,EAAE,SAAS,IAAI,MAAM,WAAW,OAAO,MAAM;AACnD,WAAO,EAAE,SAAS,SAAS,wBAAwB,GAAG;AAAA,EACxD,CAAC;AAED,QAAM,cAAoC,OAAO,GAAG,SAAS;AAC3D,UAAM,MAAM,UAAU,GAAG,cAAc;AACvC,UAAM,UAAU,MAAM,MAAM,SAAsB,KAAK,IAAI,CAAC,EAAE,aAAa,IAAI;AAC/E,QAAI,CAAC,WAAW,QAAQ,EAAE,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GAAG;AAEpE,aAAO,EAAE,KAAK,EAAE,OAAO,mBAAmB,aAAa,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG;AAAA,IAC7E;AACA,MAAE,IAAI,aAAa,QAAQ,CAAC;AAC5B,MAAE,IAAI,eAAe,QAAQ,EAAE;AAC/B,UAAM,KAAK;AAAA,EACb;AAQA,QAAM,YAAkC,OAAO,MAAM;AACnD,UAAM,SAAS,IAAI,CAAC;AACpB,UAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG;AAC7B,UAAM,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,QAAQ,OAAO,MAAM;AAC/D,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,MAAM,MAAM,MAAM,QAAQ;AAAA,MAC9B;AAAA,MACA,SAAS;AAAA,QACP,eAAe,UAAU,EAAE,IAAI,aAAa,CAAC;AAAA,QAC7C,QAAQ;AAAA,QACR,gBAAgB,EAAE,IAAI,OAAO,cAAc,KAAK;AAAA,MAClD;AAAA,MACA,MAAM,WAAW,SAAS,WAAW,SAAS,SAAY,MAAM,EAAE,IAAI,YAAY;AAAA,IACpF,CAAC;AACD,UAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,IAAI,QAAQ,IAAI,cAAc,KAAK,mBAAmB,CAAC;AACrG,UAAM,YAAY,IAAI,QAAQ,IAAI,cAAc;AAChD,QAAI,UAAW,SAAQ,IAAI,gBAAgB,SAAS;AACpD,WAAO,IAAI,SAAS,IAAI,MAAM,EAAE,QAAQ,IAAI,QAAQ,QAAQ,CAAC;AAAA,EAC/D;AAGA,QAAM,kBAAwC,OAAO,GAAG,SAAS;AAC/D,UAAM,OAAO,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,UAAU,EAAE,IAAI,WAAW;AACnF,QAAI,CAAC,QAAQ,EAAE,IAAI,WAAW,GAAG,eAAe,QAAQ;AACtD,aAAO,EAAE,KAAK,EAAE,OAAO,4BAA4B,GAAG,GAAG;AAAA,IAC3D;AACA,UAAM,KAAK;AAAA,EACb;AAEA,SAAO,EAAE,QAAQ,aAAa,iBAAiB,UAAU;AAC3D;","names":[]}
1
+ {"version":3,"sources":["../src/hono.ts"],"sourcesContent":["/**\n * ./hono — thin adapter over ./auth + ./http for Hono apps on Workers.\n * Mounts the browser SSO routes and exposes the auth middlewares.\n * Everything platform-shaped stays in the core modules.\n */\nimport { Hono } from \"hono\";\nimport type { Context, Env as HonoEnv, MiddlewareHandler } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport { createPkce, openData, randomToken, sealData, verifyAccessToken } from \"./auth\";\nimport type { Principal } from \"./auth\";\nimport { GodxSsoClient, loadIssuer } from \"./http\";\n\nexport interface GodxSsoConfig {\n issuer: string;\n serviceSlug: string;\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n sessionSecret: string;\n /**\n * Requested scopes. Default `openid profile email offline_access`: `offline_access` gets a\n * refresh token, so a session outlives one access token (see `POST <mount>/refresh`).\n */\n scope?: string;\n /**\n * How long a session with a refresh token may live, in seconds (default 30 days = the\n * platform's refresh-token lifetime). The access token inside is still short-lived.\n */\n sessionMaxAge?: number;\n /** Token audience = your ServiceInstance slug (defaults to serviceSlug). */\n audience?: string;\n /**\n * console_organization_id to fall back on when the login was not started\n * from a platform launch URL (which carries organization_context_id).\n * Useful in local dev; leave unset in production multi-org setups.\n */\n defaultOrganizationContextId?: string;\n}\n\n/** p = verified principal, at = access token, rt = refresh token (offline_access). */\ntype SessionData = { p: Principal; at: string; rt?: string };\n\nconst DEFAULT_SCOPE = \"openid profile email offline_access\";\nconst DEFAULT_SESSION_MAX_AGE = 30 * 24 * 3600;\n\nconst TXN_COOKIE = \"gx_txn\";\n\n/**\n * `return_to` for `/login`: only a same-origin path (\"/mail?x=1\") is kept — anything that a\n * browser could resolve to another origin (\"//evil\", \"/\\\\evil\", \"https://…\", control\n * characters) is dropped, so the login flow can never become an open redirect.\n */\nexport function safeReturnTo(raw: string | undefined | null): string | undefined {\n if (!raw || raw.length > 2048 || !/^\\/(?![/\\\\])/.test(raw) || /[\\u0000-\\u001f\\u007f\\\\]/.test(raw)) {\n return undefined;\n }\n const base = \"https://return-to.invalid\";\n const url = new URL(raw, base);\n return url.origin === base ? url.pathname + url.search + url.hash : undefined;\n}\n\nconst SESSION_COOKIE = \"gx_sess\";\n\nexport type GodxVariables = { principal: Principal; accessToken: string };\n\nexport function godxSso<E extends HonoEnv & { Variables: GodxVariables }>(\n getConfig: (env: E[\"Bindings\"]) => GodxSsoConfig,\n) {\n const client = (c: Context<E>) => new GodxSsoClient(getConfig(c.env));\n const cfg = (c: Context<E>) => getConfig(c.env);\n const secure = (c: Context<E>) => c.req.url.startsWith(\"https:\");\n\n /**\n * With a refresh token the cookie lives as long as the refresh token may (the access token\n * inside still expires on time — requireAuth checks it); without one, exactly as long as the\n * access token, as before.\n */\n const writeSession = async (c: Context<E>, data: SessionData) => {\n const config = cfg(c);\n const ttl = data.rt\n ? (config.sessionMaxAge ?? DEFAULT_SESSION_MAX_AGE)\n : Math.max(0, data.p.expiresAt - Math.floor(Date.now() / 1000));\n setCookie(c, SESSION_COOKIE, await sealData(data, config.sessionSecret, ttl), {\n httpOnly: true,\n secure: secure(c),\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: ttl,\n });\n };\n\n const routes = new Hono<E>();\n\n routes.get(\"/login\", async (c) => {\n const config = cfg(c);\n const pkce = await createPkce();\n const state = randomToken();\n const nonce = randomToken();\n // The platform launch URL carries organization_context_id; forward it, or\n // fall back to the configured default (single-org / local dev).\n const organizationContextId =\n c.req.query(\"organization_context_id\") ?? config.defaultOrganizationContextId;\n // Where to land after the callback (an expired session re-signs in on the page it was on).\n const returnTo = safeReturnTo(c.req.query(\"return_to\"));\n setCookie(c, TXN_COOKIE, await sealData({ v: pkce.verifier, s: state, r: returnTo }, config.sessionSecret, 300), {\n httpOnly: true,\n secure: secure(c),\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: 300,\n });\n return c.redirect(\n await client(c).authorizeUrl({\n state,\n nonce,\n codeChallenge: pkce.challenge,\n scope: config.scope ?? DEFAULT_SCOPE,\n organizationContextId,\n }),\n );\n });\n\n // Every failure lands back on the app with a human-readable description —\n // the SPA renders it; a bare 400 helps nobody.\n const failRedirect = (c: Context<E>, error: string, description: string) =>\n c.redirect(\n `/?sso_error=${encodeURIComponent(error)}&sso_error_description=${encodeURIComponent(description)}`,\n );\n\n routes.get(\"/callback\", async (c) => {\n const config = cfg(c);\n\n // The platform denied the launch (access_denied etc.) and already wrote a\n // localized, human-readable explanation into error_description. Show it.\n const platformError = c.req.query(\"error\");\n if (platformError) {\n return failRedirect(\n c,\n platformError,\n c.req.query(\"error_description\") ?? `サインインが拒否されました(${platformError})。`,\n );\n }\n\n const code = c.req.query(\"code\");\n const state = c.req.query(\"state\");\n const txnCookie = getCookie(c, TXN_COOKIE);\n deleteCookie(c, TXN_COOKIE, { path: \"/\" });\n\n const txn = txnCookie ? await openData<{ v: string; s: string; r?: string }>(txnCookie, config.sessionSecret) : null;\n if (!code || !state || !txn || txn.s !== state) {\n return failRedirect(\n c,\n \"transaction_invalid\",\n \"ログイン処理の有効期限が切れたか、途中で中断されました。もう一度サインインしてください。\",\n );\n }\n\n let tokens;\n try {\n tokens = await client(c).exchangeCode({ code, codeVerifier: txn.v });\n } catch (e) {\n return failRedirect(\n c,\n \"token_exchange_failed\",\n `認可コードの交換に失敗しました(認可コードは一度しか使えません)。もう一度サインインしてください。詳細: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n let verified;\n try {\n verified = await verifyAccessToken(tokens.access_token, config);\n } catch (e) {\n return failRedirect(\n c,\n \"token_verification_failed\",\n `トークンの検証に失敗しました。サービス設定(issuer / service_slug)を確認してください。詳細: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n // One userinfo call at login so the UI has a display name without further round-trips.\n let principal = verified;\n try {\n const info = await client(c).userinfo(tokens.access_token);\n principal = Object.freeze({ ...verified, name: info.name, email: info.email });\n } catch {\n // profile display data is optional; the verified principal stands on its own\n }\n\n await writeSession(c, { p: principal, at: tokens.access_token, rt: tokens.refresh_token });\n return c.redirect(safeReturnTo(txn.r) ?? \"/\");\n });\n\n /**\n * Renews an expired (or expiring) session with its refresh token. The platform ROTATES refresh\n * tokens and revokes the whole family when an old one is replayed, so this is a dedicated route\n * the browser calls ONCE (single-flight) when an API answers 401 `refreshable: true`, then\n * retries — refreshing inside `requireAuth` would let parallel requests replay the same token\n * and log the user out.\n *\n * 204 = renewed (new cookie set). 401 = no refresh token, or the platform refused it (expired,\n * revoked, membership gone): the session cookie is cleared and the app should sign in again.\n */\n routes.post(\"/refresh\", async (c) => {\n const config = cfg(c);\n const raw = getCookie(c, SESSION_COOKIE);\n const session = raw ? await openData<SessionData>(raw, config.sessionSecret) : null;\n if (!session?.rt) return c.json({ error: \"unauthenticated\", refreshable: false }, 401);\n try {\n const tokens = await client(c).refresh(session.rt);\n const verified = await verifyAccessToken(tokens.access_token, config);\n // Display data came from userinfo at login; the same person, so it carries over.\n const principal = Object.freeze({ ...verified, name: session.p.name, email: session.p.email });\n await writeSession(c, { p: principal, at: tokens.access_token, rt: tokens.refresh_token ?? session.rt });\n return c.body(null, 204);\n } catch (e) {\n // The platform's reason (invalid_grant, a revoked family, …) is the only clue an operator gets.\n console.warn(\"godx-sso: refresh failed\", e instanceof Error ? e.message : String(e));\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n return c.json({ error: \"unauthenticated\", refreshable: false }, 401);\n }\n });\n\n routes.get(\"/logout\", async (c) => {\n const config = cfg(c);\n const raw = getCookie(c, SESSION_COOKIE);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n if (raw) {\n const session = await openData<SessionData>(raw, config.sessionSecret);\n if (session) {\n await client(c).revoke(session.at).catch(() => {});\n // The refresh token outlives the access token by weeks: it must not survive a sign-out.\n if (session.rt) await client(c).revoke(session.rt).catch(() => {});\n }\n }\n const { metadata } = await loadIssuer(config.issuer);\n return c.redirect(metadata.end_session_endpoint ?? \"/\");\n });\n\n const requireAuth: MiddlewareHandler<E> = async (c, next) => {\n const raw = getCookie(c, SESSION_COOKIE);\n const session = raw ? await openData<SessionData>(raw, cfg(c).sessionSecret) : null;\n if (!session || session.p.expiresAt <= Math.floor(Date.now() / 1000)) {\n // `refreshable`: POST <mount>/refresh can renew it without a new sign-in.\n return c.json({ error: \"unauthenticated\", refreshable: !!session?.rt }, 401);\n }\n c.set(\"principal\", session.p);\n c.set(\"accessToken\", session.at);\n await next();\n };\n\n /**\n * Forwards /api/dock/v1/* to the platform with the session's Bearer token —\n * the GoDX Dock's `apiBase` lane: no CORS, no token in the page. Mount it\n * BEHIND requireAuth and BEFORE any requireWritable (Dock actions are the\n * platform's calls to authorize, not this app's writes).\n */\n const dockProxy: MiddlewareHandler<E> = async (c) => {\n const config = cfg(c);\n const url = new URL(c.req.url);\n const target = new URL(url.pathname + url.search, config.issuer);\n const method = c.req.method;\n const res = await fetch(target, {\n method,\n headers: {\n Authorization: `Bearer ${c.get(\"accessToken\")}`,\n Accept: \"application/json\",\n \"Content-Type\": c.req.header(\"Content-Type\") ?? \"application/json\",\n },\n body: method === \"GET\" || method === \"HEAD\" ? undefined : await c.req.arrayBuffer(),\n });\n const headers = new Headers({ \"Content-Type\": res.headers.get(\"Content-Type\") ?? \"application/json\" });\n const requestId = res.headers.get(\"X-Request-Id\");\n if (requestId) headers.set(\"X-Request-Id\", requestId);\n return new Response(res.body, { status: res.status, headers });\n };\n\n /** Enforces organization_access_mode: read-only orgs may not write. */\n const requireWritable: MiddlewareHandler<E> = async (c, next) => {\n const safe = c.req.method === \"GET\" || c.req.method === \"HEAD\" || c.req.method === \"OPTIONS\";\n if (!safe && c.get(\"principal\")?.accessMode !== \"full\") {\n return c.json({ error: \"organization is read-only\" }, 403);\n }\n await next();\n };\n\n return { routes, requireAuth, requireWritable, dockProxy };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/server-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "GoDX server-side SDK (Workers/Node) — holds service credentials; for browser code use @godxjp/web-sdk. Counterpart of godx-jp/php-sdk.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {